From a60bdd7d2921ca10b0a5ae3f750b402e12981004 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Wed, 30 Sep 2009 08:59:15 -0300
Subject: Added queue abstraction to Orchestra.
---
activesupport/lib/active_support/orchestra.rb | 57 +++++++------
activesupport/test/orchestra_test.rb | 111 +++++++++++---------------
2 files changed, 80 insertions(+), 88 deletions(-)
diff --git a/activesupport/lib/active_support/orchestra.rb b/activesupport/lib/active_support/orchestra.rb
index efe30669d8..66097a7bec 100644
--- a/activesupport/lib/active_support/orchestra.rb
+++ b/activesupport/lib/active_support/orchestra.rb
@@ -29,8 +29,9 @@ module ActiveSupport
# and is available at ActiveSupport::Orchestra::Listener.
#
module Orchestra
+ mattr_accessor :queue
+
@stacked_events = Hash.new { |h,k| h[k] = [] }
- @listeners = []
def self.instrument(name, payload=nil)
stack = @stacked_events[Thread.current.object_id]
@@ -41,15 +42,11 @@ module ActiveSupport
ensure
event.finish!
stack.delete(event)
- @listeners.each { |s| s.push(event) }
- end
-
- def self.register(listener)
- @listeners << listener
+ queue.push(event)
end
- def self.unregister(listener)
- @listeners.delete(listener)
+ def self.subscribe(pattern=nil, &block)
+ queue.subscribe(pattern, &block)
end
class Event
@@ -69,35 +66,47 @@ module ActiveSupport
end
end
- class Listener
- attr_reader :mutex, :signaler, :thread
-
+ # This is a default queue implementation that ships with Orchestra. It
+ # consumes events in a thread and publish them to all registered subscribers.
+ #
+ class LittleFanout
def initialize
- @mutex, @signaler = Mutex.new, ConditionVariable.new
- @stream = []
+ @listeners, @stream = [], []
+
@thread = Thread.new do
loop do
- (event = @stream.shift) ? consume(event) : wait
+ (event = @stream.shift) ? consume(event) : Thread.stop
end
end
end
- def wait
- @mutex.synchronize do
- @signaler.wait(@mutex)
- end
+ def push(event)
+ @stream.push(event)
+ @thread.run
end
- def push(event)
- @mutex.synchronize do
- @stream.push(event)
- @signaler.broadcast
- end
+ def subscribe(pattern=nil, &block)
+ @listeners << Listener.new(pattern, &block)
end
def consume(event)
- raise NotImplementedError
+ @listeners.each { |l| l.publish(event) }
+ end
+
+ class Listener
+ def initialize(pattern, &block)
+ @pattern = pattern
+ @subscriber = block
+ end
+
+ def publish(event)
+ unless @pattern && event.name.to_s !~ @pattern
+ @subscriber.call(event)
+ end
+ end
end
end
end
+
+ Orchestra.queue = Orchestra::LittleFanout.new
end
diff --git a/activesupport/test/orchestra_test.rb b/activesupport/test/orchestra_test.rb
index 683cc36f6a..e343d6322b 100644
--- a/activesupport/test/orchestra_test.rb
+++ b/activesupport/test/orchestra_test.rb
@@ -1,5 +1,12 @@
require 'abstract_unit'
+# Allow LittleFanout to be cleaned.
+class ActiveSupport::Orchestra::LittleFanout
+ def clear
+ @listeners.clear
+ end
+end
+
class OrchestraEventTest < Test::Unit::TestCase
def setup
@parent = ActiveSupport::Orchestra::Event.new(:parent)
@@ -34,12 +41,12 @@ end
class OrchestraMainTest < Test::Unit::TestCase
def setup
- @listener = []
- ActiveSupport::Orchestra.register @listener
+ @events = []
+ ActiveSupport::Orchestra.subscribe { |event| @events << event }
end
def teardown
- ActiveSupport::Orchestra.unregister @listener
+ ActiveSupport::Orchestra.queue.clear
end
def test_orchestra_allows_any_action_to_be_instrumented
@@ -65,9 +72,9 @@ class OrchestraMainTest < Test::Unit::TestCase
1 + 1
end
- assert_equal 1, @listener.size
- assert_equal :awesome, @listener.last.name
- assert_equal "orchestra", @listener.last.payload
+ assert_equal 1, @events.size
+ assert_equal :awesome, @events.last.name
+ assert_equal "orchestra", @events.last.payload
end
def test_nested_events_can_be_instrumented
@@ -76,18 +83,18 @@ class OrchestraMainTest < Test::Unit::TestCase
sleep(0.1)
end
- assert_equal 1, @listener.size
- assert_equal :wot, @listener.first.name
- assert_equal "child", @listener.first.payload
+ assert_equal 1, @events.size
+ assert_equal :wot, @events.first.name
+ assert_equal "child", @events.first.payload
- assert_nil @listener.first.parent.duration
- assert_in_delta 100, @listener.first.duration, 30
+ assert_nil @events.first.parent.duration
+ assert_in_delta 100, @events.first.duration, 30
end
- assert_equal 2, @listener.size
- assert_equal :awesome, @listener.last.name
- assert_equal "orchestra", @listener.last.payload
- assert_in_delta 100, @listener.first.parent.duration, 30
+ assert_equal 2, @events.size
+ assert_equal :awesome, @events.last.name
+ assert_equal "orchestra", @events.last.payload
+ assert_in_delta 100, @events.first.parent.duration, 30
end
def test_event_is_pushed_even_if_block_fails
@@ -95,67 +102,43 @@ class OrchestraMainTest < Test::Unit::TestCase
raise "OMG"
end rescue RuntimeError
- assert_equal 1, @listener.size
- assert_equal :awesome, @listener.last.name
- assert_equal "orchestra", @listener.last.payload
+ assert_equal 1, @events.size
+ assert_equal :awesome, @events.last.name
+ assert_equal "orchestra", @events.last.payload
end
-end
-
-class OrchestraListenerTest < Test::Unit::TestCase
- class MyListener < ActiveSupport::Orchestra::Listener
- attr_reader :consumed
- def consume(event)
- @consumed ||= []
- @consumed << event
- end
- end
+ def test_subscriber_with_pattern
+ @another = []
+ ActiveSupport::Orchestra.subscribe(/cache/) { |event| @another << event }
- def setup
- @listener = MyListener.new
- ActiveSupport::Orchestra.register @listener
- end
+ ActiveSupport::Orchestra.instrument(:something){ 0 }
+ ActiveSupport::Orchestra.instrument(:cache){ 10 }
- def teardown
- ActiveSupport::Orchestra.unregister @listener
- end
-
- def test_thread_is_exposed_by_listener
- assert_kind_of Thread, @listener.thread
- end
-
- def test_event_is_consumed_when_an_action_is_instrumented
- ActiveSupport::Orchestra.instrument(:sum) do
- 1 + 1
- end
sleep 0.1
- assert_equal 1, @listener.consumed.size
- assert_equal :sum, @listener.consumed.first.name
- assert_equal 2, @listener.consumed.first.result
+
+ assert_equal 1, @another.size
+ assert_equal :cache, @another.first.name
+ assert_equal 10, @another.first.result
end
- def test_with_sevaral_consumers_and_several_events
- @another = MyListener.new
- ActiveSupport::Orchestra.register @another
+ def test_with_several_consumers_and_several_events
+ @another = []
+ ActiveSupport::Orchestra.subscribe { |event| @another << event }
1.upto(100) do |i|
- ActiveSupport::Orchestra.instrument(:value) do
- i
- end
+ ActiveSupport::Orchestra.instrument(:value){ i }
end
sleep 0.1
- assert_equal 100, @listener.consumed.size
- assert_equal :value, @listener.consumed.first.name
- assert_equal 1, @listener.consumed.first.result
- assert_equal 100, @listener.consumed.last.result
-
- assert_equal 100, @another.consumed.size
- assert_equal :value, @another.consumed.first.name
- assert_equal 1, @another.consumed.first.result
- assert_equal 100, @another.consumed.last.result
- ensure
- ActiveSupport::Orchestra.unregister @another
+ assert_equal 100, @events.size
+ assert_equal :value, @events.first.name
+ assert_equal 1, @events.first.result
+ assert_equal 100, @events.last.result
+
+ assert_equal 100, @another.size
+ assert_equal :value, @another.first.name
+ assert_equal 1, @another.first.result
+ assert_equal 100, @another.last.result
end
end
--
cgit v1.2.3
From 7b5225a529cb9693f3bed8e6023de0d5348efca5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Thu, 1 Oct 2009 19:00:22 -0300
Subject: Abstract publishing, subscribing and instrumenting in Orchestra.
---
activesupport/lib/active_support/orchestra.rb | 74 +++++++++++++++++++++------
1 file changed, 59 insertions(+), 15 deletions(-)
diff --git a/activesupport/lib/active_support/orchestra.rb b/activesupport/lib/active_support/orchestra.rb
index 66097a7bec..f41f55877b 100644
--- a/activesupport/lib/active_support/orchestra.rb
+++ b/activesupport/lib/active_support/orchestra.rb
@@ -1,4 +1,5 @@
require 'thread'
+require 'active_support/core_ext/module/delegation'
module ActiveSupport
# Orchestra provides an instrumentation API for Ruby. To instrument an action
@@ -31,22 +32,65 @@ module ActiveSupport
module Orchestra
mattr_accessor :queue
- @stacked_events = Hash.new { |h,k| h[k] = [] }
-
- def self.instrument(name, payload=nil)
- stack = @stacked_events[Thread.current.object_id]
- event = Event.new(name, stack.last, payload)
- stack << event
- event.result = yield
- event
- ensure
- event.finish!
- stack.delete(event)
- queue.push(event)
+ class << self
+ delegate :instrument, :to => :instrumenter
+
+ def instrumenter
+ Thread.current[:orchestra_instrumeter] ||= Instrumenter.new(publisher)
+ end
+
+ def publisher
+ @publisher ||= Publisher.new(queue)
+ end
+
+ def subscribe(pattern=nil, &block)
+ Subscriber.new(queue).bind(pattern).subscribe(&block)
+ end
+ end
+
+ class Instrumenter
+ def initialize(publisher)
+ @publisher = publisher
+ @stack = []
+ end
+
+ def instrument(name, payload=nil)
+ event = Event.new(name, @stack.last, payload)
+ @stack << event
+ event.result = yield
+ event
+ ensure
+ event.finish!
+ @stack.pop
+ @publisher.publish(event)
+ end
end
- def self.subscribe(pattern=nil, &block)
- queue.subscribe(pattern, &block)
+ class Publisher
+ def initialize(queue)
+ @queue = queue
+ end
+
+ def publish(event)
+ @queue.publish(event)
+ end
+ end
+
+ class Subscriber
+ def initialize(queue)
+ @queue = queue
+ end
+
+ def bind(pattern)
+ @pattern = pattern
+ self
+ end
+
+ def subscribe
+ @queue.subscribe(@pattern) do |event|
+ yield event
+ end
+ end
end
class Event
@@ -80,7 +124,7 @@ module ActiveSupport
end
end
- def push(event)
+ def publish(event)
@stream.push(event)
@thread.run
end
--
cgit v1.2.3
From 7b7796e23d12b526fa35976c514da91169dd2566 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Thu, 1 Oct 2009 21:14:38 -0300
Subject: Events are created inside threads.
---
activesupport/lib/active_support/orchestra.rb | 51 ++++++++-----------
activesupport/test/orchestra_test.rb | 73 +++++++++------------------
2 files changed, 46 insertions(+), 78 deletions(-)
diff --git a/activesupport/lib/active_support/orchestra.rb b/activesupport/lib/active_support/orchestra.rb
index f41f55877b..553be68d99 100644
--- a/activesupport/lib/active_support/orchestra.rb
+++ b/activesupport/lib/active_support/orchestra.rb
@@ -51,18 +51,15 @@ module ActiveSupport
class Instrumenter
def initialize(publisher)
@publisher = publisher
- @stack = []
end
- def instrument(name, payload=nil)
- event = Event.new(name, @stack.last, payload)
- @stack << event
- event.result = yield
- event
+ def instrument(name, payload={})
+ payload[:time] = Time.now
+ payload[:thread_id] = Thread.current.object_id
+ payload[:result] = yield
ensure
- event.finish!
- @stack.pop
- @publisher.publish(event)
+ payload[:duration] = 1000 * (Time.now.to_f - payload[:time].to_f)
+ @publisher.publish(name, payload)
end
end
@@ -71,8 +68,8 @@ module ActiveSupport
@queue = queue
end
- def publish(event)
- @queue.publish(event)
+ def publish(name, payload)
+ @queue.publish(name, payload)
end
end
@@ -87,26 +84,22 @@ module ActiveSupport
end
def subscribe
- @queue.subscribe(@pattern) do |event|
- yield event
+ @queue.subscribe(@pattern) do |name, payload|
+ yield Event.new(name, payload)
end
end
end
class Event
- attr_reader :name, :time, :duration, :parent, :thread_id, :payload
- attr_accessor :result
+ attr_reader :name, :time, :duration, :thread_id, :result, :payload
- def initialize(name, parent=nil, payload=nil)
+ def initialize(name, payload)
@name = name
- @time = Time.now
- @thread_id = Thread.current.object_id
- @parent = parent
- @payload = payload
- end
-
- def finish!
- @duration = 1000 * (Time.now.to_f - @time.to_f)
+ @payload = payload.dup
+ @time = @payload.delete(:time)
+ @thread_id = @payload.delete(:thread_id)
+ @result = @payload.delete(:result)
+ @duration = @payload.delete(:duration)
end
end
@@ -124,7 +117,7 @@ module ActiveSupport
end
end
- def publish(event)
+ def publish(*event)
@stream.push(event)
@thread.run
end
@@ -134,7 +127,7 @@ module ActiveSupport
end
def consume(event)
- @listeners.each { |l| l.publish(event) }
+ @listeners.each { |l| l.publish(*event) }
end
class Listener
@@ -143,9 +136,9 @@ module ActiveSupport
@subscriber = block
end
- def publish(event)
- unless @pattern && event.name.to_s !~ @pattern
- @subscriber.call(event)
+ def publish(name, payload)
+ unless @pattern && name.to_s !~ @pattern
+ @subscriber.call(name, payload)
end
end
end
diff --git a/activesupport/test/orchestra_test.rb b/activesupport/test/orchestra_test.rb
index e343d6322b..608531416c 100644
--- a/activesupport/test/orchestra_test.rb
+++ b/activesupport/test/orchestra_test.rb
@@ -8,103 +8,78 @@ class ActiveSupport::Orchestra::LittleFanout
end
class OrchestraEventTest < Test::Unit::TestCase
- def setup
- @parent = ActiveSupport::Orchestra::Event.new(:parent)
+ def test_events_are_initialized_with_name_and_payload
+ event = ActiveSupport::Orchestra::Event.new(:foo, :payload => :bar)
+ assert_equal :foo, event.name
+ assert_equal Hash[:payload => :bar], event.payload
end
- def test_initialization_with_name_and_parent_and_payload
- event = ActiveSupport::Orchestra::Event.new(:awesome, @parent, :payload => "orchestra")
- assert_equal(:awesome, event.name)
- assert_equal(@parent, event.parent)
- assert_equal({ :payload => "orchestra" }, event.payload)
- end
+ def test_events_consumes_information_given_as_payload
+ event = ActiveSupport::Orchestra::Event.new(:foo,
+ :time => (time = Time.now), :result => 1, :duration => 10)
- def test_thread_id_is_set_on_initialization
- event = ActiveSupport::Orchestra::Event.new(:awesome)
- assert_equal Thread.current.object_id, event.thread_id
- end
-
- def test_current_time_is_set_on_initialization
- previous_time = Time.now.utc
- event = ActiveSupport::Orchestra::Event.new(:awesome)
- assert_kind_of Time, event.time
- assert event.time.to_f >= previous_time.to_f
- end
-
- def test_duration_is_set_when_event_finishes
- event = ActiveSupport::Orchestra::Event.new(:awesome)
- sleep(0.1)
- event.finish!
- assert_in_delta 100, event.duration, 30
+ assert_equal Hash.new, event.payload
+ assert_equal time, event.time
+ assert_equal 1, event.result
+ assert_equal 10, event.duration
end
end
class OrchestraMainTest < Test::Unit::TestCase
def setup
@events = []
+ Thread.abort_on_exception = true
ActiveSupport::Orchestra.subscribe { |event| @events << event }
end
def teardown
+ Thread.abort_on_exception = false
ActiveSupport::Orchestra.queue.clear
end
- def test_orchestra_allows_any_action_to_be_instrumented
- event = ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do
- sleep(0.1)
- end
-
- assert_equal :awesome, event.name
- assert_equal "orchestra", event.payload
- assert_in_delta 100, event.duration, 30
- end
-
- def test_block_result_is_stored
- event = ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do
+ def test_orchestra_returns_action_result
+ result = ActiveSupport::Orchestra.instrument(:awesome, :payload => "orchestra") do
1 + 1
end
- assert_equal 2, event.result
+ assert_equal 2, result
end
def test_events_are_published_to_a_listener
- event = ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do
+ ActiveSupport::Orchestra.instrument(:awesome, :payload => "orchestra") do
1 + 1
end
assert_equal 1, @events.size
assert_equal :awesome, @events.last.name
- assert_equal "orchestra", @events.last.payload
+ assert_equal Hash[:payload => "orchestra"], @events.last.payload
end
def test_nested_events_can_be_instrumented
- ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do
- ActiveSupport::Orchestra.instrument(:wot, "child") do
+ ActiveSupport::Orchestra.instrument(:awesome, :payload => "orchestra") do
+ ActiveSupport::Orchestra.instrument(:wot, :payload => "child") do
sleep(0.1)
end
assert_equal 1, @events.size
assert_equal :wot, @events.first.name
- assert_equal "child", @events.first.payload
-
- assert_nil @events.first.parent.duration
+ assert_equal Hash[:payload => "child"], @events.first.payload
assert_in_delta 100, @events.first.duration, 30
end
assert_equal 2, @events.size
assert_equal :awesome, @events.last.name
- assert_equal "orchestra", @events.last.payload
- assert_in_delta 100, @events.first.parent.duration, 30
+ assert_equal Hash[:payload => "orchestra"], @events.last.payload
end
def test_event_is_pushed_even_if_block_fails
- ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do
+ ActiveSupport::Orchestra.instrument(:awesome, :payload => "orchestra") do
raise "OMG"
end rescue RuntimeError
assert_equal 1, @events.size
assert_equal :awesome, @events.last.name
- assert_equal "orchestra", @events.last.payload
+ assert_equal Hash[:payload => "orchestra"], @events.last.payload
end
def test_subscriber_with_pattern
--
cgit v1.2.3
From 5d0f8abc003cc6edfdb471ada05754580725b353 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Tue, 6 Oct 2009 09:42:42 -0300
Subject: Orchestra listeners have their own queue.
---
activesupport/lib/active_support/orchestra.rb | 56 +++++++++++++++++----------
activesupport/test/orchestra_test.rb | 14 +++++--
2 files changed, 47 insertions(+), 23 deletions(-)
diff --git a/activesupport/lib/active_support/orchestra.rb b/activesupport/lib/active_support/orchestra.rb
index 553be68d99..7c9c3074e3 100644
--- a/activesupport/lib/active_support/orchestra.rb
+++ b/activesupport/lib/active_support/orchestra.rb
@@ -3,31 +3,41 @@ require 'active_support/core_ext/module/delegation'
module ActiveSupport
# Orchestra provides an instrumentation API for Ruby. To instrument an action
- # in Ruby you just need to:
+ # in Ruby you just need to do:
#
# ActiveSupport::Orchestra.instrument(:render, :extra => :information) do
# render :text => "Foo"
# end
#
- # Those actions are consumed by listeners. A listener is anything that responds
- # to push. You can even register an array:
+ # You can consume those events and the information they provide by registering
+ # a subscriber. For instance, let's store all instrumented events in an array:
#
- # @listener = []
- # ActiveSupport::Orchestra.register @listener
+ # @events = []
+ #
+ # ActiveSupport::Orchestra.subscribe do |event|
+ # @events << event
+ # end
#
# ActiveSupport::Orchestra.instrument(:render, :extra => :information) do
# render :text => "Foo"
# end
#
- # event #=> ActiveSupport::Orchestra::Event
+ # event = @events.first
+ # event.class #=> ActiveSupport::Orchestra::Event
# event.name #=> :render
# event.duration #=> 10 (in miliseconds)
# event.result #=> "Foo"
# event.payload #=> { :extra => :information }
#
- # Orchestra ships with a default listener implementation which puts events in
- # a stream and consume them in a Thread. This implementation is thread safe
- # and is available at ActiveSupport::Orchestra::Listener.
+ # When subscribing to Orchestra, you can pass a pattern, to only consume
+ # events that match the pattern:
+ #
+ # ActiveSupport::Orchestra.subscribe(/render/) do |event|
+ # @render_events << event
+ # end
+ #
+ # Orchestra ships with a queue implementation that consumes and publish events
+ # to subscribers in a thread. You can use any queue implementation you want.
#
module Orchestra
mattr_accessor :queue
@@ -108,37 +118,43 @@ module ActiveSupport
#
class LittleFanout
def initialize
- @listeners, @stream = [], []
-
- @thread = Thread.new do
- loop do
- (event = @stream.shift) ? consume(event) : Thread.stop
- end
- end
+ @listeners, @stream = [], Queue.new
+ @thread = Thread.new { consume }
end
def publish(*event)
@stream.push(event)
- @thread.run
end
def subscribe(pattern=nil, &block)
@listeners << Listener.new(pattern, &block)
end
- def consume(event)
- @listeners.each { |l| l.publish(*event) }
+ def consume
+ while event = @stream.shift
+ @listeners.each { |l| l.publish(*event) }
+ end
end
class Listener
+ attr_reader :thread
+
def initialize(pattern, &block)
@pattern = pattern
@subscriber = block
+ @queue = Queue.new
+ @thread = Thread.new { consume }
end
def publish(name, payload)
unless @pattern && name.to_s !~ @pattern
- @subscriber.call(name, payload)
+ @queue << [name, payload]
+ end
+ end
+
+ def consume
+ while event = @queue.shift
+ @subscriber.call(*event)
end
end
end
diff --git a/activesupport/test/orchestra_test.rb b/activesupport/test/orchestra_test.rb
index 608531416c..810d99ebeb 100644
--- a/activesupport/test/orchestra_test.rb
+++ b/activesupport/test/orchestra_test.rb
@@ -50,6 +50,8 @@ class OrchestraMainTest < Test::Unit::TestCase
1 + 1
end
+ sleep(0.1)
+
assert_equal 1, @events.size
assert_equal :awesome, @events.last.name
assert_equal Hash[:payload => "orchestra"], @events.last.payload
@@ -58,18 +60,22 @@ class OrchestraMainTest < Test::Unit::TestCase
def test_nested_events_can_be_instrumented
ActiveSupport::Orchestra.instrument(:awesome, :payload => "orchestra") do
ActiveSupport::Orchestra.instrument(:wot, :payload => "child") do
- sleep(0.1)
+ 1 + 1
end
+ sleep(0.1)
+
assert_equal 1, @events.size
assert_equal :wot, @events.first.name
assert_equal Hash[:payload => "child"], @events.first.payload
- assert_in_delta 100, @events.first.duration, 30
end
+ sleep(0.1)
+
assert_equal 2, @events.size
assert_equal :awesome, @events.last.name
assert_equal Hash[:payload => "orchestra"], @events.last.payload
+ assert_in_delta 100, @events.last.duration, 70
end
def test_event_is_pushed_even_if_block_fails
@@ -77,6 +83,8 @@ class OrchestraMainTest < Test::Unit::TestCase
raise "OMG"
end rescue RuntimeError
+ sleep(0.1)
+
assert_equal 1, @events.size
assert_equal :awesome, @events.last.name
assert_equal Hash[:payload => "orchestra"], @events.last.payload
@@ -89,7 +97,7 @@ class OrchestraMainTest < Test::Unit::TestCase
ActiveSupport::Orchestra.instrument(:something){ 0 }
ActiveSupport::Orchestra.instrument(:cache){ 10 }
- sleep 0.1
+ sleep(0.1)
assert_equal 1, @another.size
assert_equal :cache, @another.first.name
--
cgit v1.2.3
From af0d1fa8920793a95fae456d1f5debdc50287eb3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Wed, 7 Oct 2009 11:17:50 -0300
Subject: Update Orchestra instrumentations and move part of logging to
Orchestra.
---
actionpack/lib/abstract_controller/logger.rb | 12 +++++-----
.../lib/action_controller/caching/fragments.rb | 26 ++++++---------------
actionpack/lib/action_controller/caching/pages.rb | 10 ++------
actionpack/lib/action_controller/instrument.rb | 6 +++++
actionpack/lib/action_view/template/template.rb | 2 +-
actionpack/test/controller/caching_test.rb | 9 +-------
activerecord/lib/active_record/base.rb | 2 ++
.../connection_adapters/abstract_adapter.rb | 14 ++++-------
activerecord/lib/active_record/instrument.rb | 5 ++++
activesupport/lib/active_support/cache.rb | 11 ++++-----
.../lib/active_support/cache/mem_cache_store.rb | 11 +++++----
activesupport/lib/active_support/orchestra.rb | 4 ++--
activesupport/test/orchestra_test.rb | 27 +++++++++++++++++++---
13 files changed, 72 insertions(+), 67 deletions(-)
create mode 100644 actionpack/lib/action_controller/instrument.rb
create mode 100644 activerecord/lib/active_record/instrument.rb
diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb
index f4d017b8e5..cf1a6f1240 100644
--- a/actionpack/lib/abstract_controller/logger.rb
+++ b/actionpack/lib/abstract_controller/logger.rb
@@ -10,14 +10,14 @@ module AbstractController
module ClassMethods #:nodoc:
# Logs a message appending the value measured.
- def log_with_time(message, time, log_level=::Logger::DEBUG)
+ def log(message, log_level=::Logger::DEBUG)
return unless logger && logger.level >= log_level
- logger.add(log_level, "#{message} (%.1fms)" % time)
+ logger.add(log_level, message)
end
# Silences the logger for the duration of the block.
def silence
- old_logger_level, logger.level = logger.level, ::Logger::ERROR if logge
+ old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger
yield
ensure
logger.level = old_logger_level if logger
@@ -47,7 +47,7 @@ module AbstractController
# Override process_action in the AbstractController::Base
# to log details about the method.
def process_action(action)
- event = ActiveSupport::Orchestra.instrument(:process_action,
+ result = ActiveSupport::Orchestra.instrument(:process_action,
:controller => self, :action => action) do
super
end
@@ -56,13 +56,13 @@ module AbstractController
log = DelayedLog.new do
"\n\nProcessing #{self.class.name}\##{action_name} " \
"to #{request.formats} (for #{request_origin}) " \
- "(%.1fms) [#{request.method.to_s.upcase}]" % event.duration
+ "[#{request.method.to_s.upcase}]"
end
logger.info(log)
end
- event.result
+ result
end
private
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 59e24619e3..00fb55f843 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -51,40 +51,32 @@ module ActionController #:nodoc:
# Writes content to the location signified by key (see expire_fragment for acceptable formats)
def write_fragment(key, content, options = nil)
return content unless cache_configured?
-
key = fragment_cache_key(key)
- event = ActiveSupport::Orchestra.instrument(:write_fragment, :key => key) do
+
+ ActiveSupport::Orchestra.instrument(:write_fragment, :key => key) do
cache_store.write(key, content, options)
end
-
- self.class.log_with_time("Cached fragment miss: #{key}", event.duration)
content
end
# Reads a cached fragment from the location signified by key (see expire_fragment for acceptable formats)
def read_fragment(key, options = nil)
return unless cache_configured?
-
key = fragment_cache_key(key)
- event = ActiveSupport::Orchestra.instrument(:read_fragment, :key => key) do
+
+ ActiveSupport::Orchestra.instrument(:read_fragment, :key => key) do
cache_store.read(key, options)
end
-
- self.class.log_with_time("Cached fragment hit: #{key}", event.duration)
- event.result
end
# Check if a cached fragment from the location signified by key exists (see expire_fragment for acceptable formats)
def fragment_exist?(key, options = nil)
return unless cache_configured?
-
key = fragment_cache_key(key)
- event = ActiveSupport::Orchestra.instrument(:fragment_exist?, :key => key) do
+
+ ActiveSupport::Orchestra.instrument(:fragment_exist?, :key => key) do
cache_store.exist?(key, options)
end
-
- self.class.log_with_time("Cached fragment exists?: #{key}", event.duration)
- event.result
end
# Removes fragments from the cache.
@@ -106,11 +98,10 @@ module ActionController #:nodoc:
# method (or delete_matched, for Regexp keys.)
def expire_fragment(key, options = nil)
return unless cache_configured?
-
key = fragment_cache_key(key) unless key.is_a?(Regexp)
message = nil
- event = ActiveSupport::Orchestra.instrument(:expire_fragment, :key => key) do
+ ActiveSupport::Orchestra.instrument(:expire_fragment, :key => key) do
if key.is_a?(Regexp)
message = "Expired fragments matching: #{key.source}"
cache_store.delete_matched(key, options)
@@ -119,9 +110,6 @@ module ActionController #:nodoc:
cache_store.delete(key, options)
end
end
-
- self.class.log_with_time(message, event.duration)
- event.result
end
end
end
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index 4fb154470f..72551d1fd6 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -64,12 +64,9 @@ module ActionController #:nodoc:
return unless perform_caching
path = page_cache_path(path)
- event = ActiveSupport::Orchestra.instrument(:expire_page, :path => path) do
+ ActiveSupport::Orchestra.instrument(:expire_page, :path => path) do
File.delete(path) if File.exist?(path)
end
-
- log_with_time("Expired page: #{path}", event.duration)
- event.result
end
# Manually cache the +content+ in the key determined by +path+. Example:
@@ -78,13 +75,10 @@ module ActionController #:nodoc:
return unless perform_caching
path = page_cache_path(path)
- event = ActiveSupport::Orchestra.instrument(:cache_page, :path => path) do
+ ActiveSupport::Orchestra.instrument(:cache_page, :path => path) do
FileUtils.makedirs(File.dirname(path))
File.open(path, "wb+") { |f| f.write(content) }
end
-
- log_with_time("Cached page: #{path}", event.duration)
- event.result
end
# Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
diff --git a/actionpack/lib/action_controller/instrument.rb b/actionpack/lib/action_controller/instrument.rb
new file mode 100644
index 0000000000..00888c425f
--- /dev/null
+++ b/actionpack/lib/action_controller/instrument.rb
@@ -0,0 +1,6 @@
+require 'active_support/orchestra'
+
+ActiveSupport::Orchestra.subscribe(/(read|write|cache|expire|exist)_(fragment|page)\??/) do |event|
+ human_name = event.name.to_s.humanize
+ ActionController::Base.log("#{human_name} (%.1fms)" % event.duration)
+end
diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb
index 0f64c23649..84105bd05d 100644
--- a/actionpack/lib/action_view/template/template.rb
+++ b/actionpack/lib/action_view/template/template.rb
@@ -30,7 +30,7 @@ module ActionView
ActiveSupport::Orchestra.instrument(:render_template, :identifier => identifier) do
method_name = compile(locals, view)
view.send(method_name, locals, &block)
- end.result
+ end
rescue Exception => e
if e.is_a?(TemplateError)
e.sub_template_of(self)
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 495b431307..99d7192a9e 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -628,20 +628,13 @@ class FragmentCachingTest < ActionController::TestCase
def test_fragment_for_logging
fragment_computed = false
-
- listener = []
- ActiveSupport::Orchestra.register listener
+ ActiveSupport::Orchestra.queue.expects(:publish).times(4)
buffer = 'generated till now -> '
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
- assert_equal 1, listener.count { |e| e.name == :fragment_exist? }
- assert_equal 1, listener.count { |e| e.name == :write_fragment }
-
assert fragment_computed
assert_equal 'generated till now -> ', buffer
- ensure
- ActiveSupport::Orchestra.unregister listener
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 283aa7ddfc..eae59dfad7 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1,3 +1,4 @@
+require 'benchmark'
require 'yaml'
require 'set'
require 'active_support/dependencies'
@@ -11,6 +12,7 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/symbol'
+require 'active_support/core_ext/benchmark'
require 'active_support/core_ext/object/metaclass'
module ActiveRecord #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 694e1e561c..b411fe7526 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,4 +1,3 @@
-require 'benchmark'
require 'date'
require 'bigdecimal'
require 'bigdecimal/util'
@@ -12,8 +11,6 @@ require 'active_record/connection_adapters/abstract/connection_pool'
require 'active_record/connection_adapters/abstract/connection_specification'
require 'active_record/connection_adapters/abstract/query_cache'
-require 'active_support/core_ext/benchmark'
-
module ActiveRecord
module ConnectionAdapters # :nodoc:
# ActiveRecord supports multiple database systems. AbstractAdapter and
@@ -33,6 +30,7 @@ module ActiveRecord
include Quoting, DatabaseStatements, SchemaStatements
include QueryCache
include ActiveSupport::Callbacks
+
define_callbacks :checkout, :checkin
@@row_even = true
@@ -193,6 +191,7 @@ module ActiveRecord
end
def log_info(sql, name, ms)
+ @runtime += ms
if @logger && @logger.debug?
name = '%s (%.1fms)' % [name || 'SQL', ms]
@logger.debug(format_log_entry(name, sql.squeeze(' ')))
@@ -200,13 +199,8 @@ module ActiveRecord
end
protected
- def log(sql, name)
- event = ActiveSupport::Orchestra.instrument(:sql, :sql => sql, :name => name) do
- yield if block_given?
- end
- @runtime += event.duration
- log_info(sql, name, event.duration)
- event.result
+ def log(sql, name, &block)
+ ActiveSupport::Orchestra.instrument(:sql, :sql => sql, :name => name, &block)
rescue Exception => e
# Log message and raise exception.
# Set last_verification to 0, so that connection gets verified
diff --git a/activerecord/lib/active_record/instrument.rb b/activerecord/lib/active_record/instrument.rb
new file mode 100644
index 0000000000..06d3e40080
--- /dev/null
+++ b/activerecord/lib/active_record/instrument.rb
@@ -0,0 +1,5 @@
+require 'active_support/orchestra'
+
+ActiveSupport::Orchestra.subscribe("sql") do |event|
+ ActiveRecord::Base.connection.log_info(event.payload[:sql], event.payload[:name], event.duration)
+end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index a415686020..dfd53462af 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -219,7 +219,6 @@ module ActiveSupport
end
def increment(key, amount = 1)
- log("incrementing", key, amount)
if num = read(key)
write(key, num + amount)
else
@@ -228,7 +227,6 @@ module ActiveSupport
end
def decrement(key, amount = 1)
- log("decrementing", key, amount)
if num = read(key)
write(key, num - amount)
else
@@ -247,13 +245,14 @@ module ActiveSupport
payload = { :key => key }
payload.merge!(options) if options.is_a?(Hash)
- event = ActiveSupport::Orchestra.instrument(:"cache_#{operation}", payload, &block)
- log("#{operation} (%.1fms)" % event.duration, key, options)
- event.result
+ # Cache events should be logged or not?
+ # log(operation, key, options)
+ ActiveSupport::Orchestra.instrument(:"cache_#{operation}", payload, &block)
end
def log(operation, key, options)
- logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger && !silence?
+ return unless logger && !silence?
+ logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}")
end
end
end
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 516af99ce9..bec9de86ed 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -103,17 +103,20 @@ module ActiveSupport
end
def increment(key, amount = 1) # :nodoc:
- log("incrementing", key, amount)
+ response = instrument(:increment, key, :amount => amount) do
+ @data.incr(key, amount)
+ end
- response = @data.incr(key, amount)
response == Response::NOT_FOUND ? nil : response
rescue MemCache::MemCacheError
nil
end
def decrement(key, amount = 1) # :nodoc:
- log("decrement", key, amount)
- response = @data.decr(key, amount)
+ response = instrument(:decrement, key, :amount => amount) do
+ @data.decr(key, amount)
+ end
+
response == Response::NOT_FOUND ? nil : response
rescue MemCache::MemCacheError
nil
diff --git a/activesupport/lib/active_support/orchestra.rb b/activesupport/lib/active_support/orchestra.rb
index 7c9c3074e3..7d4c25669d 100644
--- a/activesupport/lib/active_support/orchestra.rb
+++ b/activesupport/lib/active_support/orchestra.rb
@@ -66,7 +66,7 @@ module ActiveSupport
def instrument(name, payload={})
payload[:time] = Time.now
payload[:thread_id] = Thread.current.object_id
- payload[:result] = yield
+ payload[:result] = yield if block_given?
ensure
payload[:duration] = 1000 * (Time.now.to_f - payload[:time].to_f)
@publisher.publish(name, payload)
@@ -147,7 +147,7 @@ module ActiveSupport
end
def publish(name, payload)
- unless @pattern && name.to_s !~ @pattern
+ unless @pattern && !(@pattern === name.to_s)
@queue << [name, payload]
end
end
diff --git a/activesupport/test/orchestra_test.rb b/activesupport/test/orchestra_test.rb
index 810d99ebeb..7a6e9208b4 100644
--- a/activesupport/test/orchestra_test.rb
+++ b/activesupport/test/orchestra_test.rb
@@ -90,18 +90,39 @@ class OrchestraMainTest < Test::Unit::TestCase
assert_equal Hash[:payload => "orchestra"], @events.last.payload
end
+ def test_event_is_pushed_even_without_block
+ ActiveSupport::Orchestra.instrument(:awesome, :payload => "orchestra")
+ sleep(0.1)
+
+ assert_equal 1, @events.size
+ assert_equal :awesome, @events.last.name
+ assert_equal Hash[:payload => "orchestra"], @events.last.payload
+ end
+
def test_subscriber_with_pattern
@another = []
- ActiveSupport::Orchestra.subscribe(/cache/) { |event| @another << event }
+ ActiveSupport::Orchestra.subscribe("cache"){ |event| @another << event }
+ ActiveSupport::Orchestra.instrument(:cache){ 1 }
+
+ sleep(0.1)
+
+ assert_equal 1, @another.size
+ assert_equal :cache, @another.first.name
+ assert_equal 1, @another.first.result
+ end
+
+ def test_subscriber_with_pattern_as_regexp
+ @another = []
+ ActiveSupport::Orchestra.subscribe(/cache/){ |event| @another << event }
ActiveSupport::Orchestra.instrument(:something){ 0 }
- ActiveSupport::Orchestra.instrument(:cache){ 10 }
+ ActiveSupport::Orchestra.instrument(:cache){ 1 }
sleep(0.1)
assert_equal 1, @another.size
assert_equal :cache, @another.first.name
- assert_equal 10, @another.first.result
+ assert_equal 1, @another.first.result
end
def test_with_several_consumers_and_several_events
--
cgit v1.2.3
From 8f59d7a8d8e736d7f4b6730020c197d008fb0779 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Fri, 9 Oct 2009 08:22:42 -0300
Subject: Instrument cache store events only if required.
---
actionpack/test/controller/caching_test.rb | 2 +-
activesupport/lib/active_support/cache.rb | 24 ++++++++++++++++++------
2 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 99d7192a9e..3408233b66 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -628,7 +628,7 @@ class FragmentCachingTest < ActionController::TestCase
def test_fragment_for_logging
fragment_computed = false
- ActiveSupport::Orchestra.queue.expects(:publish).times(4)
+ ActiveSupport::Orchestra.queue.expects(:publish).times(2)
buffer = 'generated till now -> '
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index dfd53462af..a2717499e7 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -105,7 +105,7 @@ module ActiveSupport
# cache.write("city", "Duckburgh")
# cache.read("city") # => "Duckburgh"
class Store
- cattr_accessor :logger
+ cattr_accessor :logger, :instance_writter => false
attr_reader :silence
alias :silence? :silence
@@ -122,6 +122,15 @@ module ActiveSupport
@silence = previous_silence
end
+ # Set to true if cache stores should be instrumented. By default is false.
+ def self.instrument=(boolean)
+ Thread.current[:instrument_cache_store] = boolean
+ end
+
+ def self.instrument
+ Thread.current[:instrument_cache_store] || false
+ end
+
# Fetches data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned.
#
@@ -242,12 +251,15 @@ module ActiveSupport
end
def instrument(operation, key, options, &block)
- payload = { :key => key }
- payload.merge!(options) if options.is_a?(Hash)
+ log(operation, key, options)
- # Cache events should be logged or not?
- # log(operation, key, options)
- ActiveSupport::Orchestra.instrument(:"cache_#{operation}", payload, &block)
+ if self.class.instrument
+ payload = { :key => key }
+ payload.merge!(options) if options.is_a?(Hash)
+ ActiveSupport::Orchestra.instrument(:"cache_#{operation}", payload, &block)
+ else
+ yield
+ end
end
def log(operation, key, options)
--
cgit v1.2.3
From aeaabc6d2d6f9faaa98057f33c0635d8add54461 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Fri, 9 Oct 2009 09:17:08 -0300
Subject: Configure Orchestra on initialization.
---
railties/lib/rails/application.rb | 11 +++++++
railties/lib/rails/configuration.rb | 8 +++++
railties/test/application/orchestra_test.rb | 49 +++++++++++++++++++++++++++++
3 files changed, 68 insertions(+)
create mode 100644 railties/test/application/orchestra_test.rb
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index a0e5d6a5a5..8e0e28c362 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -487,5 +487,16 @@ module Rails
Rails::Generators.options.deep_merge! config.generators.options
end
end
+
+ # For each framework, search for instrument file with Orchestra hooks.
+ #
+ initializer :load_orchestra_instrumentation do
+ config.frameworks.each do |framework|
+ begin
+ require "#{framework}/instrument"
+ rescue LoadError => e
+ end
+ end
+ end
end
end
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index 322590f108..3d9ae46786 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -290,6 +290,14 @@ module Rails
end
end
+ # Allows Orchestra queue to be modified.
+ #
+ # config.orchestra.queue = MyNewQueue.new
+ #
+ def orchestra
+ ActiveSupport::Orchestra
+ end
+
class Generators #:nodoc:
attr_accessor :aliases, :options, :colorize_logging
diff --git a/railties/test/application/orchestra_test.rb b/railties/test/application/orchestra_test.rb
new file mode 100644
index 0000000000..38a06be741
--- /dev/null
+++ b/railties/test/application/orchestra_test.rb
@@ -0,0 +1,49 @@
+require "isolation/abstract_unit"
+require "active_support/orchestra"
+
+module ApplicationTests
+ class OrchestraTest < Test::Unit::TestCase
+
+ class MyQueue
+ attr_reader :events, :subscribers
+
+ def initialize
+ @events = []
+ @subscribers = []
+ end
+
+ def publish(name, payload=nil)
+ @events << name
+ end
+
+ def subscribe(pattern=nil, &block)
+ @subscribers << pattern
+ end
+ end
+
+ def setup
+ build_app
+ boot_rails
+
+ Rails::Initializer.run do |c|
+ c.orchestra.queue = MyQueue.new
+ c.orchestra.subscribe(/listening/) do
+ puts "Cool"
+ end
+ end
+ end
+
+ test "new queue is set" do
+ ActiveSupport::Orchestra.instrument(:foo)
+ assert_equal :foo, ActiveSupport::Orchestra.queue.events.first
+ end
+
+ test "frameworks subscribers are loaded" do
+ assert_equal 1, ActiveSupport::Orchestra.queue.subscribers.count { |s| s == "sql" }
+ end
+
+ test "configuration subscribers are loaded" do
+ assert_equal 1, ActiveSupport::Orchestra.queue.subscribers.count { |s| s == /listening/ }
+ end
+ end
+end
--
cgit v1.2.3
From a15e02d44ac2afb27a7e8e652c98a796d271b645 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Fri, 9 Oct 2009 09:52:25 -0300
Subject: Unify benchmark APIs.
---
actionpack/lib/abstract_controller/logger.rb | 18 +----
actionpack/lib/action_controller/instrument.rb | 6 +-
.../lib/action_controller/metal/benchmarking.rb | 23 +-----
actionpack/lib/action_view/helpers.rb | 6 +-
.../lib/action_view/helpers/benchmark_helper.rb | 54 --------------
actionpack/test/template/benchmark_helper_test.rb | 86 ---------------------
activerecord/lib/active_record/base.rb | 36 +--------
activerecord/test/cases/base_test.rb | 10 +--
activesupport/lib/active_support/autoload.rb | 1 +
activesupport/lib/active_support/benchmarkable.rb | 59 +++++++++++++++
activesupport/test/benchmarkable_test.rb | 87 ++++++++++++++++++++++
11 files changed, 167 insertions(+), 219 deletions(-)
delete mode 100644 actionpack/lib/action_view/helpers/benchmark_helper.rb
delete mode 100644 actionpack/test/template/benchmark_helper_test.rb
create mode 100644 activesupport/lib/active_support/benchmarkable.rb
create mode 100644 activesupport/test/benchmarkable_test.rb
diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb
index cf1a6f1240..ebdeab3dfb 100644
--- a/actionpack/lib/abstract_controller/logger.rb
+++ b/actionpack/lib/abstract_controller/logger.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/logger'
+require 'active_support/benchmarkable'
module AbstractController
module Logger
@@ -6,22 +7,7 @@ module AbstractController
included do
cattr_accessor :logger
- end
-
- module ClassMethods #:nodoc:
- # Logs a message appending the value measured.
- def log(message, log_level=::Logger::DEBUG)
- return unless logger && logger.level >= log_level
- logger.add(log_level, message)
- end
-
- # Silences the logger for the duration of the block.
- def silence
- old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger
- yield
- ensure
- logger.level = old_logger_level if logger
- end
+ extend ActiveSupport::Benchmarkable
end
# A class that allows you to defer expensive processing
diff --git a/actionpack/lib/action_controller/instrument.rb b/actionpack/lib/action_controller/instrument.rb
index 00888c425f..2c87ed7391 100644
--- a/actionpack/lib/action_controller/instrument.rb
+++ b/actionpack/lib/action_controller/instrument.rb
@@ -1,6 +1,8 @@
require 'active_support/orchestra'
ActiveSupport::Orchestra.subscribe(/(read|write|cache|expire|exist)_(fragment|page)\??/) do |event|
- human_name = event.name.to_s.humanize
- ActionController::Base.log("#{human_name} (%.1fms)" % event.duration)
+ if logger = ActionController::Base.logger
+ human_name = event.name.to_s.humanize
+ logger.info("#{human_name} (%.1fms)" % event.duration)
+ end
end
diff --git a/actionpack/lib/action_controller/metal/benchmarking.rb b/actionpack/lib/action_controller/metal/benchmarking.rb
index d4cb1e122d..e58df69172 100644
--- a/actionpack/lib/action_controller/metal/benchmarking.rb
+++ b/actionpack/lib/action_controller/metal/benchmarking.rb
@@ -1,4 +1,4 @@
-require 'benchmark'
+require 'active_support/core_ext/benchmark'
module ActionController #:nodoc:
# The benchmarking module times the performance of actions and reports to the logger. If the Active Record
@@ -6,25 +6,6 @@ module ActionController #:nodoc:
module Benchmarking #:nodoc:
extend ActiveSupport::Concern
- module ClassMethods
- # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it
- # (unless use_silence is set to false).
- #
- # The benchmark is only recorded if the current level of the logger matches the log_level, which makes it
- # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
- # will only be conducted if the log level is low enough.
- def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
- if logger && logger.level == log_level
- result = nil
- ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
- logger.add(log_level, "#{title} (#{('%.1f' % ms)}ms)")
- result
- else
- yield
- end
- end
- end
-
protected
def render(*args, &block)
if logger
@@ -45,7 +26,7 @@ module ActionController #:nodoc:
else
super
end
- end
+ end
private
def process_action(*args)
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index d63e8602f1..3d088678fc 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -1,10 +1,11 @@
+require 'active_support/benchmarkable'
+
module ActionView #:nodoc:
module Helpers #:nodoc:
autoload :ActiveModelHelper, 'action_view/helpers/active_model_helper'
autoload :AjaxHelper, 'action_view/helpers/ajax_helper'
autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper'
autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper'
- autoload :BenchmarkHelper, 'action_view/helpers/benchmark_helper'
autoload :CacheHelper, 'action_view/helpers/cache_helper'
autoload :CaptureHelper, 'action_view/helpers/capture_helper'
autoload :DateHelper, 'action_view/helpers/date_helper'
@@ -33,10 +34,11 @@ module ActionView #:nodoc:
include SanitizeHelper::ClassMethods
end
+ include ActiveSupport::Benchmarkable
+
include ActiveModelHelper
include AssetTagHelper
include AtomFeedHelper
- include BenchmarkHelper
include CacheHelper
include CaptureHelper
include DateHelper
diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb
deleted file mode 100644
index c13a069a35..0000000000
--- a/actionpack/lib/action_view/helpers/benchmark_helper.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'active_support/core_ext/benchmark'
-
-module ActionView
- module Helpers
- # This helper offers a method to measure the execution time of a block
- # in a template.
- module BenchmarkHelper
- # Allows you to measure the execution time of a block
- # in a template and records the result to the log. Wrap this block around
- # expensive operations or possible bottlenecks to get a time reading
- # for the operation. For example, let's say you thought your file
- # processing method was taking too long; you could wrap it in a benchmark block.
- #
- # <% benchmark "Process data files" do %>
- # <%= expensive_files_operation %>
- # <% end %>
- #
- # That would add something like "Process data files (345.2ms)" to the log,
- # which you can then use to compare timings when optimizing your code.
- #
- # You may give an optional logger level as the :level option.
- # (:debug, :info, :warn, :error); the default value is :info.
- #
- # <% benchmark "Low-level files", :level => :debug do %>
- # <%= lowlevel_files_operation %>
- # <% end %>
- #
- # Finally, you can pass true as the third argument to silence all log activity
- # inside the block. This is great for boiling down a noisy block to just a single statement:
- #
- # <% benchmark "Process data files", :level => :info, :silence => true do %>
- # <%= expensive_and_chatty_files_operation %>
- # <% end %>
- def benchmark(message = "Benchmarking", options = {})
- if controller.logger
- if options.is_a?(Symbol)
- ActiveSupport::Deprecation.warn("use benchmark('#{message}', :level => :#{options}) instead", caller)
- options = { :level => options, :silence => false }
- else
- options.assert_valid_keys(:level, :silence)
- options[:level] ||= :info
- end
-
- result = nil
- ms = Benchmark.ms { result = options[:silence] ? controller.logger.silence { yield } : yield }
- controller.logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
- result
- else
- yield
- end
- end
- end
- end
-end
diff --git a/actionpack/test/template/benchmark_helper_test.rb b/actionpack/test/template/benchmark_helper_test.rb
deleted file mode 100644
index ac31fc6503..0000000000
--- a/actionpack/test/template/benchmark_helper_test.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'abstract_unit'
-require 'action_view/helpers/benchmark_helper'
-
-class BenchmarkHelperTest < ActionView::TestCase
- tests ActionView::Helpers::BenchmarkHelper
-
- def setup
- super
- controller.logger = ActiveSupport::BufferedLogger.new(StringIO.new)
- controller.logger.auto_flushing = false
- end
-
- def teardown
- controller.logger.send(:clear_buffer)
- end
-
- def test_without_block
- assert_raise(LocalJumpError) { benchmark }
- assert buffer.empty?
- end
-
- def test_defaults
- i_was_run = false
- benchmark { i_was_run = true }
- assert i_was_run
- assert_last_logged
- end
-
- def test_with_message
- i_was_run = false
- benchmark('test_run') { i_was_run = true }
- assert i_was_run
- assert_last_logged 'test_run'
- end
-
- def test_with_message_and_deprecated_level
- i_was_run = false
-
- assert_deprecated do
- benchmark('debug_run', :debug) { i_was_run = true }
- end
-
- assert i_was_run
- assert_last_logged 'debug_run'
- end
-
- def test_within_level
- controller.logger.level = ActiveSupport::BufferedLogger::DEBUG
- benchmark('included_debug_run', :level => :debug) { }
- assert_last_logged 'included_debug_run'
- end
-
- def test_outside_level
- controller.logger.level = ActiveSupport::BufferedLogger::ERROR
- benchmark('skipped_debug_run', :level => :debug) { }
- assert_no_match(/skipped_debug_run/, buffer.last)
- ensure
- controller.logger.level = ActiveSupport::BufferedLogger::DEBUG
- end
-
- def test_without_silencing
- benchmark('debug_run', :silence => false) do
- controller.logger.info "not silenced!"
- end
-
- assert_equal 2, buffer.size
- end
-
- def test_with_silencing
- benchmark('debug_run', :silence => true) do
- controller.logger.info "silenced!"
- end
-
- assert_equal 1, buffer.size
- end
-
-
- private
- def buffer
- controller.logger.send(:buffer)
- end
-
- def assert_last_logged(message = 'Benchmarking')
- assert_match(/^#{message} \(.*\)$/, buffer.last)
- end
-end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index eae59dfad7..76dbd00ad9 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1,6 +1,7 @@
require 'benchmark'
require 'yaml'
require 'set'
+require 'active_support/benchmarkable'
require 'active_support/dependencies'
require 'active_support/time'
require 'active_support/core_ext/class/attribute_accessors'
@@ -12,7 +13,6 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/symbol'
-require 'active_support/core_ext/benchmark'
require 'active_support/core_ext/object/metaclass'
module ActiveRecord #:nodoc:
@@ -1465,38 +1465,6 @@ module ActiveRecord #:nodoc:
connection.quote(object)
end
- # Log and benchmark multiple statements in a single block. Example:
- #
- # Project.benchmark("Creating project") do
- # project = Project.create("name" => "stuff")
- # project.create_manager("name" => "David")
- # project.milestones << Milestone.find(:all)
- # end
- #
- # The benchmark is only recorded if the current level of the logger is less than or equal to the log_level,
- # which makes it easy to include benchmarking statements in production software that will remain inexpensive because
- # the benchmark will only be conducted if the log level is low enough.
- #
- # The logging of the multiple statements is turned off unless use_silence is set to false.
- def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
- if logger && logger.level <= log_level
- result = nil
- ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
- logger.add(log_level, '%s (%.1fms)' % [title, ms])
- result
- else
- yield
- end
- end
-
- # Silences the logger for the duration of the block.
- def silence
- old_logger_level, logger.level = logger.level, Logger::ERROR if logger
- yield
- ensure
- logger.level = old_logger_level if logger
- end
-
# Overwrite the default class equality method to provide support for association proxies.
def ===(object)
object.is_a?(self)
@@ -3155,6 +3123,8 @@ module ActiveRecord #:nodoc:
Base.class_eval do
extend ActiveModel::Naming
extend QueryCache::ClassMethods
+ extend ActiveSupport::Benchmarkable
+
include Validations
include Locking::Optimistic, Locking::Pessimistic
include AttributeMethods
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index bfb2df313b..5c2911eca1 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -2195,9 +2195,9 @@ class BasicsTest < ActiveRecord::TestCase
log = StringIO.new
ActiveRecord::Base.logger = Logger.new(log)
ActiveRecord::Base.logger.level = Logger::WARN
- ActiveRecord::Base.benchmark("Debug Topic Count", Logger::DEBUG) { Topic.count }
- ActiveRecord::Base.benchmark("Warn Topic Count", Logger::WARN) { Topic.count }
- ActiveRecord::Base.benchmark("Error Topic Count", Logger::ERROR) { Topic.count }
+ ActiveRecord::Base.benchmark("Debug Topic Count", :level => :debug) { Topic.count }
+ ActiveRecord::Base.benchmark("Warn Topic Count", :level => :warn) { Topic.count }
+ ActiveRecord::Base.benchmark("Error Topic Count", :level => :error) { Topic.count }
assert_no_match /Debug Topic Count/, log.string
assert_match /Warn Topic Count/, log.string
assert_match /Error Topic Count/, log.string
@@ -2209,8 +2209,8 @@ class BasicsTest < ActiveRecord::TestCase
original_logger = ActiveRecord::Base.logger
log = StringIO.new
ActiveRecord::Base.logger = Logger.new(log)
- ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, true) { ActiveRecord::Base.logger.debug "Loud" }
- ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, false) { ActiveRecord::Base.logger.debug "Quiet" }
+ ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => true) { ActiveRecord::Base.logger.debug "Loud" }
+ ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" }
assert_no_match /Loud/, log.string
assert_match /Quiet/, log.string
ensure
diff --git a/activesupport/lib/active_support/autoload.rb b/activesupport/lib/active_support/autoload.rb
index a0fc2bb123..fa657ac99a 100644
--- a/activesupport/lib/active_support/autoload.rb
+++ b/activesupport/lib/active_support/autoload.rb
@@ -2,6 +2,7 @@ module ActiveSupport
autoload :BacktraceCleaner, 'active_support/backtrace_cleaner'
autoload :Base64, 'active_support/base64'
autoload :BasicObject, 'active_support/basic_object'
+ autoload :Benchmarkable, 'active_support/benchmarkable'
autoload :BufferedLogger, 'active_support/buffered_logger'
autoload :Cache, 'active_support/cache'
autoload :Callbacks, 'active_support/callbacks'
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
new file mode 100644
index 0000000000..6a41aab166
--- /dev/null
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -0,0 +1,59 @@
+require 'active_support/core_ext/benchmark'
+
+module ActiveSupport
+ module Benchmarkable
+ # Allows you to measure the execution time of a block
+ # in a template and records the result to the log. Wrap this block around
+ # expensive operations or possible bottlenecks to get a time reading
+ # for the operation. For example, let's say you thought your file
+ # processing method was taking too long; you could wrap it in a benchmark block.
+ #
+ # <% benchmark "Process data files" do %>
+ # <%= expensive_files_operation %>
+ # <% end %>
+ #
+ # That would add something like "Process data files (345.2ms)" to the log,
+ # which you can then use to compare timings when optimizing your code.
+ #
+ # You may give an optional logger level as the :level option.
+ # (:debug, :info, :warn, :error); the default value is :info.
+ #
+ # <% benchmark "Low-level files", :level => :debug do %>
+ # <%= lowlevel_files_operation %>
+ # <% end %>
+ #
+ # Finally, you can pass true as the third argument to silence all log activity
+ # inside the block. This is great for boiling down a noisy block to just a single statement:
+ #
+ # <% benchmark "Process data files", :level => :info, :silence => true do %>
+ # <%= expensive_and_chatty_files_operation %>
+ # <% end %>
+ def benchmark(message = "Benchmarking", options = {})
+ if logger
+ if options.is_a?(Symbol)
+ ActiveSupport::Deprecation.warn("use benchmark('#{message}', :level => :#{options}) instead", caller)
+ options = { :level => options, :silence => false }
+ else
+ options.assert_valid_keys(:level, :silence)
+ options[:level] ||= :info
+ end
+
+ result = nil
+ ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
+ logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
+ result
+ else
+ yield
+ end
+ end
+
+ # Silence the logger during the execution of the block.
+ #
+ def silence
+ old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger
+ yield
+ ensure
+ logger.level = old_logger_level if logger
+ end
+ end
+end
diff --git a/activesupport/test/benchmarkable_test.rb b/activesupport/test/benchmarkable_test.rb
new file mode 100644
index 0000000000..e807bcb732
--- /dev/null
+++ b/activesupport/test/benchmarkable_test.rb
@@ -0,0 +1,87 @@
+require 'abstract_unit'
+require 'action_view/helpers/benchmark_helper'
+
+class BenchmarkableTest < ActiveSupport::TestCase
+ include ActiveSupport::Benchmarkable
+
+ def teardown
+ logger.send(:clear_buffer)
+ end
+
+ def test_without_block
+ assert_raise(LocalJumpError) { benchmark }
+ assert buffer.empty?
+ end
+
+ def test_defaults
+ i_was_run = false
+ benchmark { i_was_run = true }
+ assert i_was_run
+ assert_last_logged
+ end
+
+ def test_with_message
+ i_was_run = false
+ benchmark('test_run') { i_was_run = true }
+ assert i_was_run
+ assert_last_logged 'test_run'
+ end
+
+ def test_with_message_and_deprecated_level
+ i_was_run = false
+
+ assert_deprecated do
+ benchmark('debug_run', :debug) { i_was_run = true }
+ end
+
+ assert i_was_run
+ assert_last_logged 'debug_run'
+ end
+
+ def test_within_level
+ logger.level = ActiveSupport::BufferedLogger::DEBUG
+ benchmark('included_debug_run', :level => :debug) { }
+ assert_last_logged 'included_debug_run'
+ end
+
+ def test_outside_level
+ logger.level = ActiveSupport::BufferedLogger::ERROR
+ benchmark('skipped_debug_run', :level => :debug) { }
+ assert_no_match(/skipped_debug_run/, buffer.last)
+ ensure
+ logger.level = ActiveSupport::BufferedLogger::DEBUG
+ end
+
+ def test_without_silencing
+ benchmark('debug_run', :silence => false) do
+ logger.info "not silenced!"
+ end
+
+ assert_equal 2, buffer.size
+ end
+
+ def test_with_silencing
+ benchmark('debug_run', :silence => true) do
+ logger.info "silenced!"
+ end
+
+ assert_equal 1, buffer.size
+ end
+
+ private
+ def logger
+ @logger ||= begin
+ logger = ActiveSupport::BufferedLogger.new(StringIO.new)
+ logger.auto_flushing = false
+ logger
+ end
+ end
+
+ def buffer
+ logger.send(:buffer)
+ end
+
+ def assert_last_logged(message = 'Benchmarking')
+ assert_match(/^#{message} \(.*\)$/, buffer.last)
+ end
+end
--
cgit v1.2.3
From 11f9f556b83f90e33ae516cc7a74177a9befdb0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Fri, 9 Oct 2009 22:22:17 -0300
Subject: Make Orchestra specs run on isolation.
---
activesupport/lib/active_support/orchestra.rb | 1 +
railties/test/application/orchestra_test.rb | 3 ++-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/activesupport/lib/active_support/orchestra.rb b/activesupport/lib/active_support/orchestra.rb
index 7d4c25669d..96e6ce8865 100644
--- a/activesupport/lib/active_support/orchestra.rb
+++ b/activesupport/lib/active_support/orchestra.rb
@@ -1,5 +1,6 @@
require 'thread'
require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/module/attribute_accessors'
module ActiveSupport
# Orchestra provides an instrumentation API for Ruby. To instrument an action
diff --git a/railties/test/application/orchestra_test.rb b/railties/test/application/orchestra_test.rb
index 38a06be741..fcf073bd6f 100644
--- a/railties/test/application/orchestra_test.rb
+++ b/railties/test/application/orchestra_test.rb
@@ -1,8 +1,8 @@
require "isolation/abstract_unit"
-require "active_support/orchestra"
module ApplicationTests
class OrchestraTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
class MyQueue
attr_reader :events, :subscribers
@@ -25,6 +25,7 @@ module ApplicationTests
build_app
boot_rails
+ require "active_support/orchestra"
Rails::Initializer.run do |c|
c.orchestra.queue = MyQueue.new
c.orchestra.subscribe(/listening/) do
--
cgit v1.2.3
From 5988b87c30eb0ce50c235187f5dfcfcfb98da01b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Thu, 15 Oct 2009 14:49:29 -0300
Subject: Added parent_of? method to help tracing events.
---
activesupport/lib/active_support/orchestra.rb | 5 +++++
activesupport/test/orchestra_test.rb | 22 +++++++++++++++++++---
2 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/activesupport/lib/active_support/orchestra.rb b/activesupport/lib/active_support/orchestra.rb
index 96e6ce8865..5f57127401 100644
--- a/activesupport/lib/active_support/orchestra.rb
+++ b/activesupport/lib/active_support/orchestra.rb
@@ -112,6 +112,11 @@ module ActiveSupport
@result = @payload.delete(:result)
@duration = @payload.delete(:duration)
end
+
+ def parent_of?(event)
+ start = (self.time - event.time) * 1000
+ start <= 0 && (start + self.duration >= event.duration)
+ end
end
# This is a default queue implementation that ships with Orchestra. It
diff --git a/activesupport/test/orchestra_test.rb b/activesupport/test/orchestra_test.rb
index 7a6e9208b4..1b2f98c7dd 100644
--- a/activesupport/test/orchestra_test.rb
+++ b/activesupport/test/orchestra_test.rb
@@ -9,20 +9,36 @@ end
class OrchestraEventTest < Test::Unit::TestCase
def test_events_are_initialized_with_name_and_payload
- event = ActiveSupport::Orchestra::Event.new(:foo, :payload => :bar)
+ event = event(:foo, :payload => :bar)
assert_equal :foo, event.name
assert_equal Hash[:payload => :bar], event.payload
end
def test_events_consumes_information_given_as_payload
- event = ActiveSupport::Orchestra::Event.new(:foo,
- :time => (time = Time.now), :result => 1, :duration => 10)
+ event = event(:foo, :time => (time = Time.now), :result => 1, :duration => 10)
assert_equal Hash.new, event.payload
assert_equal time, event.time
assert_equal 1, event.result
assert_equal 10, event.duration
end
+
+ def test_event_is_parent_based_on_time_frame
+ parent = event(:foo, :time => Time.utc(2009), :duration => 10000)
+ child = event(:foo, :time => Time.utc(2009, 01, 01, 0, 0, 1), :duration => 1000)
+ not_child = event(:foo, :time => Time.utc(2009, 01, 01, 0, 0, 1), :duration => 10000)
+
+ assert parent.parent_of?(child)
+ assert !child.parent_of?(parent)
+ assert !parent.parent_of?(not_child)
+ assert !not_child.parent_of?(parent)
+ end
+
+ protected
+
+ def event(*args)
+ ActiveSupport::Orchestra::Event.new(*args)
+ end
end
class OrchestraMainTest < Test::Unit::TestCase
--
cgit v1.2.3
From 2d7abe245e7a2b1717e48ef550e4083318fd7ec2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Thu, 15 Oct 2009 18:51:51 -0300
Subject: Renamed Orchestra to Notifications once again [#3321 state:resolved]
---
.gitignore | 3 +
actionmailer/lib/action_mailer/base.rb | 4 +-
actionpack/lib/abstract_controller/logger.rb | 2 +-
.../lib/action_controller/caching/fragments.rb | 8 +-
actionpack/lib/action_controller/caching/pages.rb | 4 +-
actionpack/lib/action_controller/instrument.rb | 8 -
actionpack/lib/action_controller/notifications.rb | 8 +
actionpack/lib/action_view/template/template.rb | 2 +-
actionpack/test/controller/caching_test.rb | 2 +-
.../connection_adapters/abstract_adapter.rb | 2 +-
activerecord/lib/active_record/instrument.rb | 5 -
activerecord/lib/active_record/notifications.rb | 5 +
activesupport/lib/active_support/autoload.rb | 2 +-
activesupport/lib/active_support/cache.rb | 2 +-
activesupport/lib/active_support/notifications.rb | 171 +++++++++++++++++++++
activesupport/lib/active_support/orchestra.rb | 171 ---------------------
activesupport/test/notifications_test.rb | 164 ++++++++++++++++++++
activesupport/test/orchestra_test.rb | 164 --------------------
railties/lib/rails/application.rb | 6 +-
railties/lib/rails/configuration.rb | 8 +-
railties/test/application/notifications_test.rb | 50 ++++++
railties/test/application/orchestra_test.rb | 50 ------
22 files changed, 422 insertions(+), 419 deletions(-)
delete mode 100644 actionpack/lib/action_controller/instrument.rb
create mode 100644 actionpack/lib/action_controller/notifications.rb
delete mode 100644 activerecord/lib/active_record/instrument.rb
create mode 100644 activerecord/lib/active_record/notifications.rb
create mode 100644 activesupport/lib/active_support/notifications.rb
delete mode 100644 activesupport/lib/active_support/orchestra.rb
create mode 100644 activesupport/test/notifications_test.rb
delete mode 100644 activesupport/test/orchestra_test.rb
create mode 100644 railties/test/application/notifications_test.rb
delete mode 100644 railties/test/application/orchestra_test.rb
diff --git a/.gitignore b/.gitignore
index da296e7e11..ea0d8bbba7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,9 @@ railties/guides/output
*.swp
*.swo
actionpack/bin
+activerecord/bin
vendor/gems/
*/vendor/gems/
railties/tmp
+activerecord/vendor
+actionpack/vendor
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index c0c04af51c..df3bfb3620 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -481,7 +481,7 @@ module ActionMailer #:nodoc:
# Initialize the mailer via the given +method_name+. The body will be
# rendered and a new TMail::Mail object created.
def create!(method_name, *parameters) #:nodoc:
- ActiveSupport::Orchestra.instrument(:create_mail, :name => method_name) do
+ ActiveSupport::Notifications.instrument(:create_mail, :name => method_name) do
initialize_defaults(method_name)
__send__(method_name, *parameters)
@@ -550,7 +550,7 @@ module ActionMailer #:nodoc:
logger.debug "\n#{mail.encoded}"
end
- ActiveSupport::Orchestra.instrument(:deliver_mail, :mail => @mail) do
+ ActiveSupport::Notifications.instrument(:deliver_mail, :mail => @mail) do
begin
__send__("perform_delivery_#{delivery_method}", mail) if perform_deliveries
rescue Exception => e # Net::SMTP errors or sendmail pipe errors
diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb
index ebdeab3dfb..27ba5be45f 100644
--- a/actionpack/lib/abstract_controller/logger.rb
+++ b/actionpack/lib/abstract_controller/logger.rb
@@ -33,7 +33,7 @@ module AbstractController
# Override process_action in the AbstractController::Base
# to log details about the method.
def process_action(action)
- result = ActiveSupport::Orchestra.instrument(:process_action,
+ result = ActiveSupport::Notifications.instrument(:process_action,
:controller => self, :action => action) do
super
end
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 00fb55f843..8c1167d526 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -53,7 +53,7 @@ module ActionController #:nodoc:
return content unless cache_configured?
key = fragment_cache_key(key)
- ActiveSupport::Orchestra.instrument(:write_fragment, :key => key) do
+ ActiveSupport::Notifications.instrument(:write_fragment, :key => key) do
cache_store.write(key, content, options)
end
content
@@ -64,7 +64,7 @@ module ActionController #:nodoc:
return unless cache_configured?
key = fragment_cache_key(key)
- ActiveSupport::Orchestra.instrument(:read_fragment, :key => key) do
+ ActiveSupport::Notifications.instrument(:read_fragment, :key => key) do
cache_store.read(key, options)
end
end
@@ -74,7 +74,7 @@ module ActionController #:nodoc:
return unless cache_configured?
key = fragment_cache_key(key)
- ActiveSupport::Orchestra.instrument(:fragment_exist?, :key => key) do
+ ActiveSupport::Notifications.instrument(:fragment_exist?, :key => key) do
cache_store.exist?(key, options)
end
end
@@ -101,7 +101,7 @@ module ActionController #:nodoc:
key = fragment_cache_key(key) unless key.is_a?(Regexp)
message = nil
- ActiveSupport::Orchestra.instrument(:expire_fragment, :key => key) do
+ ActiveSupport::Notifications.instrument(:expire_fragment, :key => key) do
if key.is_a?(Regexp)
message = "Expired fragments matching: #{key.source}"
cache_store.delete_matched(key, options)
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index 72551d1fd6..d46f528c7e 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -64,7 +64,7 @@ module ActionController #:nodoc:
return unless perform_caching
path = page_cache_path(path)
- ActiveSupport::Orchestra.instrument(:expire_page, :path => path) do
+ ActiveSupport::Notifications.instrument(:expire_page, :path => path) do
File.delete(path) if File.exist?(path)
end
end
@@ -75,7 +75,7 @@ module ActionController #:nodoc:
return unless perform_caching
path = page_cache_path(path)
- ActiveSupport::Orchestra.instrument(:cache_page, :path => path) do
+ ActiveSupport::Notifications.instrument(:cache_page, :path => path) do
FileUtils.makedirs(File.dirname(path))
File.open(path, "wb+") { |f| f.write(content) }
end
diff --git a/actionpack/lib/action_controller/instrument.rb b/actionpack/lib/action_controller/instrument.rb
deleted file mode 100644
index 2c87ed7391..0000000000
--- a/actionpack/lib/action_controller/instrument.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-require 'active_support/orchestra'
-
-ActiveSupport::Orchestra.subscribe(/(read|write|cache|expire|exist)_(fragment|page)\??/) do |event|
- if logger = ActionController::Base.logger
- human_name = event.name.to_s.humanize
- logger.info("#{human_name} (%.1fms)" % event.duration)
- end
-end
diff --git a/actionpack/lib/action_controller/notifications.rb b/actionpack/lib/action_controller/notifications.rb
new file mode 100644
index 0000000000..4ec88193d5
--- /dev/null
+++ b/actionpack/lib/action_controller/notifications.rb
@@ -0,0 +1,8 @@
+require 'active_support/notifications'
+
+ActiveSupport::Notifications.subscribe(/(read|write|cache|expire|exist)_(fragment|page)\??/) do |event|
+ if logger = ActionController::Base.logger
+ human_name = event.name.to_s.humanize
+ logger.info("#{human_name} (%.1fms)" % event.duration)
+ end
+end
diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb
index 84105bd05d..d1970ca3c7 100644
--- a/actionpack/lib/action_view/template/template.rb
+++ b/actionpack/lib/action_view/template/template.rb
@@ -27,7 +27,7 @@ module ActionView
end
def render(view, locals, &block)
- ActiveSupport::Orchestra.instrument(:render_template, :identifier => identifier) do
+ ActiveSupport::Notifications.instrument(:render_template, :identifier => identifier) do
method_name = compile(locals, view)
view.send(method_name, locals, &block)
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 3408233b66..df2dee8228 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -628,7 +628,7 @@ class FragmentCachingTest < ActionController::TestCase
def test_fragment_for_logging
fragment_computed = false
- ActiveSupport::Orchestra.queue.expects(:publish).times(2)
+ ActiveSupport::Notifications.queue.expects(:publish).times(2)
buffer = 'generated till now -> '
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index b411fe7526..8fae26b790 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -200,7 +200,7 @@ module ActiveRecord
protected
def log(sql, name, &block)
- ActiveSupport::Orchestra.instrument(:sql, :sql => sql, :name => name, &block)
+ ActiveSupport::Notifications.instrument(:sql, :sql => sql, :name => name, &block)
rescue Exception => e
# Log message and raise exception.
# Set last_verification to 0, so that connection gets verified
diff --git a/activerecord/lib/active_record/instrument.rb b/activerecord/lib/active_record/instrument.rb
deleted file mode 100644
index 06d3e40080..0000000000
--- a/activerecord/lib/active_record/instrument.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'active_support/orchestra'
-
-ActiveSupport::Orchestra.subscribe("sql") do |event|
- ActiveRecord::Base.connection.log_info(event.payload[:sql], event.payload[:name], event.duration)
-end
diff --git a/activerecord/lib/active_record/notifications.rb b/activerecord/lib/active_record/notifications.rb
new file mode 100644
index 0000000000..a5ce7ac524
--- /dev/null
+++ b/activerecord/lib/active_record/notifications.rb
@@ -0,0 +1,5 @@
+require 'active_support/notifications'
+
+ActiveSupport::Notifications.subscribe("sql") do |event|
+ ActiveRecord::Base.connection.log_info(event.payload[:sql], event.payload[:name], event.duration)
+end
diff --git a/activesupport/lib/active_support/autoload.rb b/activesupport/lib/active_support/autoload.rb
index fa657ac99a..63f7338a68 100644
--- a/activesupport/lib/active_support/autoload.rb
+++ b/activesupport/lib/active_support/autoload.rb
@@ -18,9 +18,9 @@ module ActiveSupport
autoload :MessageVerifier, 'active_support/message_verifier'
autoload :Multibyte, 'active_support/multibyte'
autoload :OptionMerger, 'active_support/option_merger'
- autoload :Orchestra, 'active_support/orchestra'
autoload :OrderedHash, 'active_support/ordered_hash'
autoload :OrderedOptions, 'active_support/ordered_options'
+ autoload :Notifications, 'active_support/notifications'
autoload :Rescuable, 'active_support/rescuable'
autoload :SecureRandom, 'active_support/secure_random'
autoload :StringInquirer, 'active_support/string_inquirer'
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index a2717499e7..818983fdd6 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -256,7 +256,7 @@ module ActiveSupport
if self.class.instrument
payload = { :key => key }
payload.merge!(options) if options.is_a?(Hash)
- ActiveSupport::Orchestra.instrument(:"cache_#{operation}", payload, &block)
+ ActiveSupport::Notifications.instrument(:"cache_#{operation}", payload, &block)
else
yield
end
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
new file mode 100644
index 0000000000..7e9ffca13f
--- /dev/null
+++ b/activesupport/lib/active_support/notifications.rb
@@ -0,0 +1,171 @@
+require 'thread'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/module/attribute_accessors'
+
+module ActiveSupport
+ # Notifications provides an instrumentation API for Ruby. To instrument an
+ # action in Ruby you just need to do:
+ #
+ # ActiveSupport::Notifications.instrument(:render, :extra => :information) do
+ # render :text => "Foo"
+ # end
+ #
+ # You can consume those events and the information they provide by registering
+ # a subscriber. For instance, let's store all instrumented events in an array:
+ #
+ # @events = []
+ #
+ # ActiveSupport::Notifications.subscribe do |event|
+ # @events << event
+ # end
+ #
+ # ActiveSupport::Notifications.instrument(:render, :extra => :information) do
+ # render :text => "Foo"
+ # end
+ #
+ # event = @events.first
+ # event.class #=> ActiveSupport::Notifications::Event
+ # event.name #=> :render
+ # event.duration #=> 10 (in miliseconds)
+ # event.result #=> "Foo"
+ # event.payload #=> { :extra => :information }
+ #
+ # When subscribing to Notifications, you can pass a pattern, to only consume
+ # events that match the pattern:
+ #
+ # ActiveSupport::Notifications.subscribe(/render/) do |event|
+ # @render_events << event
+ # end
+ #
+ # Notifications ships with a queue implementation that consumes and publish events
+ # to subscribers in a thread. You can use any queue implementation you want.
+ #
+ module Notifications
+ mattr_accessor :queue
+
+ class << self
+ delegate :instrument, :to => :instrumenter
+
+ def instrumenter
+ Thread.current[:notifications_instrumeter] ||= Instrumenter.new(publisher)
+ end
+
+ def publisher
+ @publisher ||= Publisher.new(queue)
+ end
+
+ def subscribe(pattern=nil, &block)
+ Subscriber.new(queue).bind(pattern).subscribe(&block)
+ end
+ end
+
+ class Instrumenter
+ def initialize(publisher)
+ @publisher = publisher
+ end
+
+ def instrument(name, payload={})
+ payload[:time] = Time.now
+ payload[:thread_id] = Thread.current.object_id
+ payload[:result] = yield if block_given?
+ ensure
+ payload[:duration] = 1000 * (Time.now.to_f - payload[:time].to_f)
+ @publisher.publish(name, payload)
+ end
+ end
+
+ class Publisher
+ def initialize(queue)
+ @queue = queue
+ end
+
+ def publish(name, payload)
+ @queue.publish(name, payload)
+ end
+ end
+
+ class Subscriber
+ def initialize(queue)
+ @queue = queue
+ end
+
+ def bind(pattern)
+ @pattern = pattern
+ self
+ end
+
+ def subscribe
+ @queue.subscribe(@pattern) do |name, payload|
+ yield Event.new(name, payload)
+ end
+ end
+ end
+
+ class Event
+ attr_reader :name, :time, :duration, :thread_id, :result, :payload
+
+ def initialize(name, payload)
+ @name = name
+ @payload = payload.dup
+ @time = @payload.delete(:time)
+ @thread_id = @payload.delete(:thread_id)
+ @result = @payload.delete(:result)
+ @duration = @payload.delete(:duration)
+ end
+
+ def parent_of?(event)
+ start = (self.time - event.time) * 1000
+ start <= 0 && (start + self.duration >= event.duration)
+ 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, @stream = [], Queue.new
+ @thread = Thread.new { consume }
+ end
+
+ def publish(*event)
+ @stream.push(event)
+ end
+
+ def subscribe(pattern=nil, &block)
+ @listeners << Listener.new(pattern, &block)
+ end
+
+ def consume
+ while event = @stream.shift
+ @listeners.each { |l| l.publish(*event) }
+ end
+ end
+
+ class Listener
+ attr_reader :thread
+
+ def initialize(pattern, &block)
+ @pattern = pattern
+ @subscriber = block
+ @queue = Queue.new
+ @thread = Thread.new { consume }
+ end
+
+ def publish(name, payload)
+ unless @pattern && !(@pattern === name.to_s)
+ @queue << [name, payload]
+ end
+ end
+
+ def consume
+ while event = @queue.shift
+ @subscriber.call(*event)
+ end
+ end
+ end
+ end
+ end
+
+ Notifications.queue = Notifications::LittleFanout.new
+end
diff --git a/activesupport/lib/active_support/orchestra.rb b/activesupport/lib/active_support/orchestra.rb
deleted file mode 100644
index 5f57127401..0000000000
--- a/activesupport/lib/active_support/orchestra.rb
+++ /dev/null
@@ -1,171 +0,0 @@
-require 'thread'
-require 'active_support/core_ext/module/delegation'
-require 'active_support/core_ext/module/attribute_accessors'
-
-module ActiveSupport
- # Orchestra provides an instrumentation API for Ruby. To instrument an action
- # in Ruby you just need to do:
- #
- # ActiveSupport::Orchestra.instrument(:render, :extra => :information) do
- # render :text => "Foo"
- # end
- #
- # You can consume those events and the information they provide by registering
- # a subscriber. For instance, let's store all instrumented events in an array:
- #
- # @events = []
- #
- # ActiveSupport::Orchestra.subscribe do |event|
- # @events << event
- # end
- #
- # ActiveSupport::Orchestra.instrument(:render, :extra => :information) do
- # render :text => "Foo"
- # end
- #
- # event = @events.first
- # event.class #=> ActiveSupport::Orchestra::Event
- # event.name #=> :render
- # event.duration #=> 10 (in miliseconds)
- # event.result #=> "Foo"
- # event.payload #=> { :extra => :information }
- #
- # When subscribing to Orchestra, you can pass a pattern, to only consume
- # events that match the pattern:
- #
- # ActiveSupport::Orchestra.subscribe(/render/) do |event|
- # @render_events << event
- # end
- #
- # Orchestra ships with a queue implementation that consumes and publish events
- # to subscribers in a thread. You can use any queue implementation you want.
- #
- module Orchestra
- mattr_accessor :queue
-
- class << self
- delegate :instrument, :to => :instrumenter
-
- def instrumenter
- Thread.current[:orchestra_instrumeter] ||= Instrumenter.new(publisher)
- end
-
- def publisher
- @publisher ||= Publisher.new(queue)
- end
-
- def subscribe(pattern=nil, &block)
- Subscriber.new(queue).bind(pattern).subscribe(&block)
- end
- end
-
- class Instrumenter
- def initialize(publisher)
- @publisher = publisher
- end
-
- def instrument(name, payload={})
- payload[:time] = Time.now
- payload[:thread_id] = Thread.current.object_id
- payload[:result] = yield if block_given?
- ensure
- payload[:duration] = 1000 * (Time.now.to_f - payload[:time].to_f)
- @publisher.publish(name, payload)
- end
- end
-
- class Publisher
- def initialize(queue)
- @queue = queue
- end
-
- def publish(name, payload)
- @queue.publish(name, payload)
- end
- end
-
- class Subscriber
- def initialize(queue)
- @queue = queue
- end
-
- def bind(pattern)
- @pattern = pattern
- self
- end
-
- def subscribe
- @queue.subscribe(@pattern) do |name, payload|
- yield Event.new(name, payload)
- end
- end
- end
-
- class Event
- attr_reader :name, :time, :duration, :thread_id, :result, :payload
-
- def initialize(name, payload)
- @name = name
- @payload = payload.dup
- @time = @payload.delete(:time)
- @thread_id = @payload.delete(:thread_id)
- @result = @payload.delete(:result)
- @duration = @payload.delete(:duration)
- end
-
- def parent_of?(event)
- start = (self.time - event.time) * 1000
- start <= 0 && (start + self.duration >= event.duration)
- end
- end
-
- # This is a default queue implementation that ships with Orchestra. It
- # consumes events in a thread and publish them to all registered subscribers.
- #
- class LittleFanout
- def initialize
- @listeners, @stream = [], Queue.new
- @thread = Thread.new { consume }
- end
-
- def publish(*event)
- @stream.push(event)
- end
-
- def subscribe(pattern=nil, &block)
- @listeners << Listener.new(pattern, &block)
- end
-
- def consume
- while event = @stream.shift
- @listeners.each { |l| l.publish(*event) }
- end
- end
-
- class Listener
- attr_reader :thread
-
- def initialize(pattern, &block)
- @pattern = pattern
- @subscriber = block
- @queue = Queue.new
- @thread = Thread.new { consume }
- end
-
- def publish(name, payload)
- unless @pattern && !(@pattern === name.to_s)
- @queue << [name, payload]
- end
- end
-
- def consume
- while event = @queue.shift
- @subscriber.call(*event)
- end
- end
- end
- end
- end
-
- Orchestra.queue = Orchestra::LittleFanout.new
-end
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
new file mode 100644
index 0000000000..561ee2b0ba
--- /dev/null
+++ b/activesupport/test/notifications_test.rb
@@ -0,0 +1,164 @@
+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_name_and_payload
+ event = event(:foo, :payload => :bar)
+ assert_equal :foo, event.name
+ assert_equal Hash[:payload => :bar], event.payload
+ end
+
+ def test_events_consumes_information_given_as_payload
+ event = event(:foo, :time => (time = Time.now), :result => 1, :duration => 10)
+
+ assert_equal Hash.new, event.payload
+ assert_equal time, event.time
+ assert_equal 1, event.result
+ assert_equal 10, event.duration
+ end
+
+ def test_event_is_parent_based_on_time_frame
+ parent = event(:foo, :time => Time.utc(2009), :duration => 10000)
+ child = event(:foo, :time => Time.utc(2009, 01, 01, 0, 0, 1), :duration => 1000)
+ not_child = event(:foo, :time => Time.utc(2009, 01, 01, 0, 0, 1), :duration => 10000)
+
+ assert parent.parent_of?(child)
+ assert !child.parent_of?(parent)
+ assert !parent.parent_of?(not_child)
+ assert !not_child.parent_of?(parent)
+ end
+
+ protected
+
+ def event(*args)
+ ActiveSupport::Notifications::Event.new(*args)
+ end
+end
+
+class NotificationsMainTest < Test::Unit::TestCase
+ def setup
+ @events = []
+ Thread.abort_on_exception = true
+ ActiveSupport::Notifications.subscribe { |event| @events << event }
+ 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
+
+ assert_equal 2, result
+ end
+
+ def test_events_are_published_to_a_listener
+ ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do
+ 1 + 1
+ end
+
+ sleep(0.1)
+
+ assert_equal 1, @events.size
+ assert_equal :awesome, @events.last.name
+ assert_equal Hash[:payload => "notifications"], @events.last.payload
+ end
+
+ def test_nested_events_can_be_instrumented
+ ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do
+ ActiveSupport::Notifications.instrument(:wot, :payload => "child") do
+ 1 + 1
+ end
+
+ sleep(0.1)
+
+ assert_equal 1, @events.size
+ assert_equal :wot, @events.first.name
+ assert_equal Hash[:payload => "child"], @events.first.payload
+ end
+
+ sleep(0.1)
+
+ assert_equal 2, @events.size
+ assert_equal :awesome, @events.last.name
+ assert_equal Hash[:payload => "notifications"], @events.last.payload
+ assert_in_delta 100, @events.last.duration, 70
+ end
+
+ def test_event_is_pushed_even_if_block_fails
+ ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do
+ raise "OMG"
+ end rescue RuntimeError
+
+ sleep(0.1)
+
+ assert_equal 1, @events.size
+ assert_equal :awesome, @events.last.name
+ assert_equal Hash[:payload => "notifications"], @events.last.payload
+ end
+
+ def test_event_is_pushed_even_without_block
+ ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications")
+ sleep(0.1)
+
+ 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
+ @another = []
+ ActiveSupport::Notifications.subscribe("cache"){ |event| @another << event }
+ ActiveSupport::Notifications.instrument(:cache){ 1 }
+
+ sleep(0.1)
+
+ assert_equal 1, @another.size
+ assert_equal :cache, @another.first.name
+ assert_equal 1, @another.first.result
+ end
+
+ def test_subscriber_with_pattern_as_regexp
+ @another = []
+ ActiveSupport::Notifications.subscribe(/cache/){ |event| @another << event }
+
+ ActiveSupport::Notifications.instrument(:something){ 0 }
+ ActiveSupport::Notifications.instrument(:cache){ 1 }
+
+ sleep(0.1)
+
+ 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 { |event| @another << event }
+
+ 1.upto(100) do |i|
+ ActiveSupport::Notifications.instrument(:value){ i }
+ end
+
+ sleep 0.1
+
+ assert_equal 100, @events.size
+ assert_equal :value, @events.first.name
+ assert_equal 1, @events.first.result
+ assert_equal 100, @events.last.result
+
+ assert_equal 100, @another.size
+ assert_equal :value, @another.first.name
+ assert_equal 1, @another.first.result
+ assert_equal 100, @another.last.result
+ end
+end
diff --git a/activesupport/test/orchestra_test.rb b/activesupport/test/orchestra_test.rb
deleted file mode 100644
index 1b2f98c7dd..0000000000
--- a/activesupport/test/orchestra_test.rb
+++ /dev/null
@@ -1,164 +0,0 @@
-require 'abstract_unit'
-
-# Allow LittleFanout to be cleaned.
-class ActiveSupport::Orchestra::LittleFanout
- def clear
- @listeners.clear
- end
-end
-
-class OrchestraEventTest < Test::Unit::TestCase
- def test_events_are_initialized_with_name_and_payload
- event = event(:foo, :payload => :bar)
- assert_equal :foo, event.name
- assert_equal Hash[:payload => :bar], event.payload
- end
-
- def test_events_consumes_information_given_as_payload
- event = event(:foo, :time => (time = Time.now), :result => 1, :duration => 10)
-
- assert_equal Hash.new, event.payload
- assert_equal time, event.time
- assert_equal 1, event.result
- assert_equal 10, event.duration
- end
-
- def test_event_is_parent_based_on_time_frame
- parent = event(:foo, :time => Time.utc(2009), :duration => 10000)
- child = event(:foo, :time => Time.utc(2009, 01, 01, 0, 0, 1), :duration => 1000)
- not_child = event(:foo, :time => Time.utc(2009, 01, 01, 0, 0, 1), :duration => 10000)
-
- assert parent.parent_of?(child)
- assert !child.parent_of?(parent)
- assert !parent.parent_of?(not_child)
- assert !not_child.parent_of?(parent)
- end
-
- protected
-
- def event(*args)
- ActiveSupport::Orchestra::Event.new(*args)
- end
-end
-
-class OrchestraMainTest < Test::Unit::TestCase
- def setup
- @events = []
- Thread.abort_on_exception = true
- ActiveSupport::Orchestra.subscribe { |event| @events << event }
- end
-
- def teardown
- Thread.abort_on_exception = false
- ActiveSupport::Orchestra.queue.clear
- end
-
- def test_orchestra_returns_action_result
- result = ActiveSupport::Orchestra.instrument(:awesome, :payload => "orchestra") do
- 1 + 1
- end
-
- assert_equal 2, result
- end
-
- def test_events_are_published_to_a_listener
- ActiveSupport::Orchestra.instrument(:awesome, :payload => "orchestra") do
- 1 + 1
- end
-
- sleep(0.1)
-
- assert_equal 1, @events.size
- assert_equal :awesome, @events.last.name
- assert_equal Hash[:payload => "orchestra"], @events.last.payload
- end
-
- def test_nested_events_can_be_instrumented
- ActiveSupport::Orchestra.instrument(:awesome, :payload => "orchestra") do
- ActiveSupport::Orchestra.instrument(:wot, :payload => "child") do
- 1 + 1
- end
-
- sleep(0.1)
-
- assert_equal 1, @events.size
- assert_equal :wot, @events.first.name
- assert_equal Hash[:payload => "child"], @events.first.payload
- end
-
- sleep(0.1)
-
- assert_equal 2, @events.size
- assert_equal :awesome, @events.last.name
- assert_equal Hash[:payload => "orchestra"], @events.last.payload
- assert_in_delta 100, @events.last.duration, 70
- end
-
- def test_event_is_pushed_even_if_block_fails
- ActiveSupport::Orchestra.instrument(:awesome, :payload => "orchestra") do
- raise "OMG"
- end rescue RuntimeError
-
- sleep(0.1)
-
- assert_equal 1, @events.size
- assert_equal :awesome, @events.last.name
- assert_equal Hash[:payload => "orchestra"], @events.last.payload
- end
-
- def test_event_is_pushed_even_without_block
- ActiveSupport::Orchestra.instrument(:awesome, :payload => "orchestra")
- sleep(0.1)
-
- assert_equal 1, @events.size
- assert_equal :awesome, @events.last.name
- assert_equal Hash[:payload => "orchestra"], @events.last.payload
- end
-
- def test_subscriber_with_pattern
- @another = []
- ActiveSupport::Orchestra.subscribe("cache"){ |event| @another << event }
- ActiveSupport::Orchestra.instrument(:cache){ 1 }
-
- sleep(0.1)
-
- assert_equal 1, @another.size
- assert_equal :cache, @another.first.name
- assert_equal 1, @another.first.result
- end
-
- def test_subscriber_with_pattern_as_regexp
- @another = []
- ActiveSupport::Orchestra.subscribe(/cache/){ |event| @another << event }
-
- ActiveSupport::Orchestra.instrument(:something){ 0 }
- ActiveSupport::Orchestra.instrument(:cache){ 1 }
-
- sleep(0.1)
-
- 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::Orchestra.subscribe { |event| @another << event }
-
- 1.upto(100) do |i|
- ActiveSupport::Orchestra.instrument(:value){ i }
- end
-
- sleep 0.1
-
- assert_equal 100, @events.size
- assert_equal :value, @events.first.name
- assert_equal 1, @events.first.result
- assert_equal 100, @events.last.result
-
- assert_equal 100, @another.size
- assert_equal :value, @another.first.name
- assert_equal 1, @another.first.result
- assert_equal 100, @another.last.result
- end
-end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 8e0e28c362..fe2dc3769e 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -488,12 +488,12 @@ module Rails
end
end
- # For each framework, search for instrument file with Orchestra hooks.
+ # For each framework, search for instrument file with Notifications hooks.
#
- initializer :load_orchestra_instrumentation do
+ initializer :load_notifications_hooks do
config.frameworks.each do |framework|
begin
- require "#{framework}/instrument"
+ require "#{framework}/notifications"
rescue LoadError => e
end
end
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index 3d9ae46786..3cb39245ff 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -290,12 +290,12 @@ module Rails
end
end
- # Allows Orchestra queue to be modified.
+ # Allows Notifications queue to be modified.
#
- # config.orchestra.queue = MyNewQueue.new
+ # config.notifications.queue = MyNewQueue.new
#
- def orchestra
- ActiveSupport::Orchestra
+ def notifications
+ ActiveSupport::Notifications
end
class Generators #:nodoc:
diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb
new file mode 100644
index 0000000000..c861d10c35
--- /dev/null
+++ b/railties/test/application/notifications_test.rb
@@ -0,0 +1,50 @@
+require "isolation/abstract_unit"
+
+module ApplicationTests
+ class NotificationsTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ class MyQueue
+ attr_reader :events, :subscribers
+
+ def initialize
+ @events = []
+ @subscribers = []
+ end
+
+ def publish(name, payload=nil)
+ @events << name
+ end
+
+ def subscribe(pattern=nil, &block)
+ @subscribers << pattern
+ end
+ end
+
+ def setup
+ build_app
+ boot_rails
+
+ require "active_support/notifications"
+ Rails::Initializer.run do |c|
+ c.notifications.queue = MyQueue.new
+ c.notifications.subscribe(/listening/) do
+ puts "Cool"
+ end
+ end
+ end
+
+ test "new queue is set" do
+ ActiveSupport::Notifications.instrument(:foo)
+ assert_equal :foo, ActiveSupport::Notifications.queue.events.first
+ end
+
+ test "frameworks subscribers are loaded" do
+ assert_equal 1, ActiveSupport::Notifications.queue.subscribers.count { |s| s == "sql" }
+ end
+
+ test "configuration subscribers are loaded" do
+ assert_equal 1, ActiveSupport::Notifications.queue.subscribers.count { |s| s == /listening/ }
+ end
+ end
+end
diff --git a/railties/test/application/orchestra_test.rb b/railties/test/application/orchestra_test.rb
deleted file mode 100644
index fcf073bd6f..0000000000
--- a/railties/test/application/orchestra_test.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require "isolation/abstract_unit"
-
-module ApplicationTests
- class OrchestraTest < Test::Unit::TestCase
- include ActiveSupport::Testing::Isolation
-
- class MyQueue
- attr_reader :events, :subscribers
-
- def initialize
- @events = []
- @subscribers = []
- end
-
- def publish(name, payload=nil)
- @events << name
- end
-
- def subscribe(pattern=nil, &block)
- @subscribers << pattern
- end
- end
-
- def setup
- build_app
- boot_rails
-
- require "active_support/orchestra"
- Rails::Initializer.run do |c|
- c.orchestra.queue = MyQueue.new
- c.orchestra.subscribe(/listening/) do
- puts "Cool"
- end
- end
- end
-
- test "new queue is set" do
- ActiveSupport::Orchestra.instrument(:foo)
- assert_equal :foo, ActiveSupport::Orchestra.queue.events.first
- end
-
- test "frameworks subscribers are loaded" do
- assert_equal 1, ActiveSupport::Orchestra.queue.subscribers.count { |s| s == "sql" }
- end
-
- test "configuration subscribers are loaded" do
- assert_equal 1, ActiveSupport::Orchestra.queue.subscribers.count { |s| s == /listening/ }
- end
- end
-end
--
cgit v1.2.3
From ef75d05829a4bbaeab4edb157194c2bd7f0ef60a Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Thu, 15 Oct 2009 17:39:19 -0700
Subject: Get rid of stray require again
---
activesupport/test/benchmarkable_test.rb | 1 -
1 file changed, 1 deletion(-)
diff --git a/activesupport/test/benchmarkable_test.rb b/activesupport/test/benchmarkable_test.rb
index e807bcb732..766956f50f 100644
--- a/activesupport/test/benchmarkable_test.rb
+++ b/activesupport/test/benchmarkable_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'action_view/helpers/benchmark_helper'
class BenchmarkableTest < ActiveSupport::TestCase
include ActiveSupport::Benchmarkable
--
cgit v1.2.3
From b0f55dc1f82b3d4fc56d44133b10926be3efa607 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Thu, 15 Oct 2009 21:51:24 -0700
Subject: Remove framework subscriber tests which depends on AR, which isn't
loaded
---
railties/test/application/notifications_test.rb | 4 ----
1 file changed, 4 deletions(-)
diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb
index c861d10c35..0fdb4a083a 100644
--- a/railties/test/application/notifications_test.rb
+++ b/railties/test/application/notifications_test.rb
@@ -39,10 +39,6 @@ module ApplicationTests
assert_equal :foo, ActiveSupport::Notifications.queue.events.first
end
- test "frameworks subscribers are loaded" do
- assert_equal 1, ActiveSupport::Notifications.queue.subscribers.count { |s| s == "sql" }
- end
-
test "configuration subscribers are loaded" do
assert_equal 1, ActiveSupport::Notifications.queue.subscribers.count { |s| s == /listening/ }
end
--
cgit v1.2.3
From 471a394215e01ffae03bbafa027a44f63e0658b8 Mon Sep 17 00:00:00 2001
From: Nick Sieger
Date: Fri, 16 Oct 2009 11:39:32 -0500
Subject: Modify connection pool callbacks to be compatible w/ new style
Signed-off-by: Yehuda Katz
---
.../connection_adapters/abstract/connection_pool.rb | 14 ++++++++------
activerecord/test/cases/pooled_connections_test.rb | 21 +++++++++++++++++++--
2 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 12253eac3f..377f2a44c5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -211,9 +211,10 @@ module ActiveRecord
# calling +checkout+ on this pool.
def checkin(conn)
@connection_mutex.synchronize do
- conn.run_callbacks :checkin
- @checked_out.delete conn
- @queue.signal
+ conn.run_callbacks :checkin do
+ @checked_out.delete conn
+ @queue.signal
+ end
end
end
@@ -255,9 +256,10 @@ module ActiveRecord
end
def checkout_and_verify(c)
- c.verify!
- c.run_callbacks :checkout
- @checked_out << c
+ c.run_callbacks :checkout do
+ c.verify!
+ @checked_out << c
+ end
c
end
end
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index bb9013c2a1..f9eea3d118 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -4,14 +4,14 @@ require "timeout"
class PooledConnectionsTest < ActiveRecord::TestCase
def setup
- super
+ @per_test_teardown = []
@connection = ActiveRecord::Base.remove_connection
end
def teardown
ActiveRecord::Base.clear_all_connections!
ActiveRecord::Base.establish_connection(@connection)
- super
+ @per_test_teardown.each {|td| td.call }
end
def checkout_connections
@@ -113,6 +113,23 @@ class PooledConnectionsTest < ActiveRecord::TestCase
assert_equal 3, after_count - before_count
end
+ def test_connection_pool_callbacks
+ checked_out, checked_in = false, false
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
+ set_callback(:checkout, :after) { checked_out = true }
+ set_callback(:checkin, :before) { checked_in = true }
+ end
+ @per_test_teardown << proc do
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
+ reset_callbacks :checkout
+ reset_callbacks :checkin
+ end
+ end
+ checkout_checkin_connections 1, 1
+ assert checked_out
+ assert checked_in
+ end
+
private
def add_record(name)
--
cgit v1.2.3
From a565c19c5ba465c74a465b55be97a4abfb760e20 Mon Sep 17 00:00:00 2001
From: Yehuda Katz
Date: Fri, 16 Oct 2009 11:26:19 -0700
Subject: Fix a bug where templates with locales were not being sorted
correctly
---
actionpack/lib/action_view/template/resolver.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index f5591ead09..7336114e1b 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -121,7 +121,7 @@ module ActionView
# # :api: plugin
def path_to_details(path)
# [:erb, :format => :html, :locale => :en, :partial => true/false]
- if m = path.match(%r'(?:^|/)(_)?[\w-]+(\.[\w-]+)*\.(\w+)$')
+ if m = path.match(%r'(?:^|/)(_)?[\w-]+((?:\.[\w-]+)*)\.(\w+)$')
partial = m[1] == '_'
details = (m[2]||"").split('.').reject { |e| e.empty? }
handler = Template.handler_class_for_extension(m[3])
--
cgit v1.2.3
From 6094e6516951444f8c3d6bb31f171af9fe96a02f Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 13:56:59 -0500
Subject: We won't be publishing tars and zips anymore
---
actionmailer/Rakefile | 6 ++----
actionpack/Rakefile | 12 +++++-------
activemodel/Rakefile | 4 +---
activerecord/Rakefile | 2 --
activeresource/Rakefile | 10 ++++------
activesupport/Rakefile | 30 ++++++++++++++----------------
6 files changed, 26 insertions(+), 38 deletions(-)
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index a756f35ee8..8a863705bb 100644
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -50,19 +50,17 @@ spec = eval(File.read('actionmailer.gemspec'))
Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
- p.need_tar = true
- p.need_zip = true
end
desc "Publish the API documentation"
-task :pgem => [:package] do
+task :pgem => [:package] do
require 'rake/contrib/sshpublisher'
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
end
desc "Publish the API documentation"
-task :pdoc => [:rdoc] do
+task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload
end
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 157e3efd3c..99bdcc95fa 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -56,7 +56,7 @@ Rake::RDocTask.new { |rdoc|
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.options << '--charset' << 'utf-8'
rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
- if ENV['DOC_FILES']
+ if ENV['DOC_FILES']
rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
else
rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
@@ -70,8 +70,6 @@ spec = eval(File.read('actionpack.gemspec'))
Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
- p.need_tar = true
- p.need_zip = true
end
task :lines do
@@ -88,10 +86,10 @@ task :lines do
codelines += 1
end
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
-
+
total_lines += lines
total_codelines += codelines
-
+
lines, codelines = 0, 0
end
@@ -112,14 +110,14 @@ task :update_js => [ :update_scriptaculous ]
# Publishing ------------------------------------------------------
desc "Publish the API documentation"
-task :pgem => [:package] do
+task :pgem => [:package] do
require 'rake/contrib/sshpublisher'
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
end
desc "Publish the API documentation"
-task :pdoc => [:rdoc] do
+task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload
end
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index fd69a557aa..8897987518 100755
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -11,7 +11,7 @@ require 'rake/testtask'
task :default => :test
-Rake::TestTask.new do |t|
+Rake::TestTask.new do |t|
t.libs << "test"
t.test_files = Dir.glob("test/cases/**/*_test.rb").sort
t.verbose = true
@@ -47,6 +47,4 @@ spec = eval(File.read('activemodel.gemspec'))
Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
- p.need_tar = true
- p.need_zip = true
end
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 1d8062e042..f7585f789b 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -195,8 +195,6 @@ spec = eval(File.read('activerecord.gemspec'))
Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
- p.need_tar = true
- p.need_zip = true
end
task :lines do
diff --git a/activeresource/Rakefile b/activeresource/Rakefile
index c87345e3b5..834d4c0c59 100644
--- a/activeresource/Rakefile
+++ b/activeresource/Rakefile
@@ -62,8 +62,6 @@ spec = eval(File.read('activeresource.gemspec'))
Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
- p.need_tar = true
- p.need_zip = true
end
task :lines do
@@ -80,10 +78,10 @@ task :lines do
codelines += 1
end
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
-
+
total_lines += lines
total_codelines += codelines
-
+
lines, codelines = 0, 0
end
@@ -94,14 +92,14 @@ end
# Publishing ------------------------------------------------------
desc "Publish the beta gem"
-task :pgem => [:package] do
+task :pgem => [:package] do
require 'rake/contrib/sshpublisher'
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
end
desc "Publish the API documentation"
-task :pdoc => [:rdoc] do
+task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload
end
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index c27167287d..d923dce3a4 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -49,8 +49,6 @@ spec = eval(File.read('activesupport.gemspec'))
Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
- p.need_tar = true
- p.need_zip = true
end
desc "Publish the beta gem"
@@ -61,7 +59,7 @@ task :pgem => [:package] do
end
desc "Publish the API documentation"
-task :pdoc => [:rdoc] do
+task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/as", "doc").upload
end
@@ -88,23 +86,23 @@ namespace :tzinfo do
Rake::Task['tzinfo:cleanup_tmp'].invoke
puts <<-EOV
*** FINAL TZINFO BUNDLING STEPS ***
-
+
1. Update TZInfo version in lib/active_support/vendor.rb
2. gem uninstall tzinfo on local system before running tests, to ensure tests are running against bundled version
-
+
If a test fails because a particular zone can't be found, it's likely because the TZInfo identifier in the
ActiveSupport::TimeZone::MAPPING hash is referencing a linked timezone instead of referencing the timezone directly.
In this case, just change the MAPPING value to the correct identifier, and unpack TZInfo again.
EOV
end
-
+
task :unpack_gem do
mkdir_p "tmp"
cd "tmp"
sh "gem unpack --version #{ENV['VERSION'] || "'> 0'"} tzinfo"
cd ".."
end
-
+
task :copy_classes => :unpack_gem do
mkdir_p "#{destination_path}/tzinfo"
cp "#{tmp_path}/lib/tzinfo.rb", destination_path
@@ -118,7 +116,7 @@ namespace :tzinfo do
end
end
end
-
+
task :copy_definitions => :unpack_gem do
definitions_path = "#{destination_path}/tzinfo/definitions/"
mkdir_p definitions_path
@@ -135,11 +133,11 @@ namespace :tzinfo do
task :cleanup_tmp do
rm_rf "tmp"
end
-
+
def comment_requires_for_excluded_classes!(file)
lines = open("#{destination_path}/#{file}") {|f| f.readlines}
updated = false
-
+
new_lines = []
lines.each do |line|
if Regexp.new("require 'tzinfo/(#{excluded_classes.join('|')})'") === line
@@ -149,29 +147,29 @@ namespace :tzinfo do
new_lines << line
end
end
-
+
if updated
open("#{destination_path}/#{file}", "w") {|f| f.write(new_lines.join)}
end
end
-
+
def version
ENV['VERSION'] ||= get_unpacked_version
end
-
+
def get_unpacked_version
m = (FileList["tmp/tzinfo-*"].to_s.match /\d+\.\d+\.\d+/)
m ? m[0] : raise(LoadError, "TZInfo gem must be installed locally. `gem install tzinfo` and try again")
end
-
+
def tmp_path
"tmp/tzinfo-#{version}"
end
-
+
def destination_path
"lib/active_support/vendor/tzinfo-#{version}"
end
-
+
def excluded_classes
%w(country country_index_definition country_info country_timezone timezone_index_definition timezone_proxy tzdataparser)
end
--
cgit v1.2.3
From 2110a524a4913815d036786aa01319fd67db0ee2 Mon Sep 17 00:00:00 2001
From: Carl Lerche
Date: Fri, 16 Oct 2009 12:49:39 -0700
Subject: Deprecate RAILS_ROOT in favor of Rails.root (which proxies to the
application's object root)
---
actionpack/lib/action_controller/metal/helpers.rb | 2 +-
.../lib/active_support/testing/isolation.rb | 2 +
railties/bin/rails | 1 +
railties/builtin/rails_info/rails/info.rb | 4 +-
railties/guides/source/2_2_release_notes.textile | 2 +-
.../source/action_controller_overview.textile | 2 +-
railties/guides/source/command_line.textile | 8 ++--
railties/lib/rails/backtrace_cleaner.rb | 4 +-
railties/lib/rails/commands/about.rb | 1 -
railties/lib/rails/commands/console.rb | 2 +-
railties/lib/rails/commands/dbconsole.rb | 2 +-
railties/lib/rails/commands/destroy.rb | 1 -
railties/lib/rails/commands/server.rb | 4 +-
railties/lib/rails/configuration.rb | 34 ++++++----------
railties/lib/rails/core.rb | 4 +-
railties/lib/rails/deprecation.rb | 17 ++++++++
railties/lib/rails/gem_dependency.rb | 2 +-
railties/lib/rails/generators.rb | 10 ++---
railties/lib/rails/generators/base.rb | 4 +-
.../rails/app/templates/config/application.rb | 2 +-
.../generators/rails/app/templates/config/boot.rb | 3 --
railties/lib/rails/initializer.rb | 1 +
railties/lib/rails/plugin/loader.rb | 2 +-
railties/lib/rails/tasks.rb | 4 +-
railties/lib/rails/tasks/databases.rake | 24 ++++++------
railties/lib/rails/tasks/framework.rake | 8 ++--
railties/lib/rails/tasks/gems.rake | 2 +-
railties/lib/rails/tasks/misc.rake | 2 +-
railties/lib/rails/test_help.rb | 2 +-
railties/test/abstract_unit.rb | 10 ++---
railties/test/application/configuration_test.rb | 8 ++--
railties/test/application/generators_test.rb | 2 +
railties/test/application/initializer_test.rb | 45 +++++++++++++++-------
railties/test/application/plugins_test.rb | 12 ++++--
railties/test/backtrace_cleaner_test.rb | 2 -
railties/test/boot_test.rb | 1 -
railties/test/generators/generators_test_helper.rb | 20 ++--------
railties/test/initializer/initialize_i18n_test.rb | 2 +
railties/test/initializer/path_test.rb | 1 +
railties/test/initializer_test.rb | 20 ----------
40 files changed, 138 insertions(+), 141 deletions(-)
create mode 100644 railties/lib/rails/deprecation.rb
delete mode 100644 railties/test/initializer_test.rb
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 7c52779064..117ea3481f 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -54,7 +54,7 @@ module ActionController
included do
# Set the default directory for helpers
extlib_inheritable_accessor(:helpers_dir) do
- defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers"
+ defined?(Rails) ? "#{Rails.root}/app/helpers" : "app/helpers"
end
end
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index c75b59c284..8f7d1c29a9 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -1,3 +1,5 @@
+require "active_support/core_ext/load_error"
+
module ActiveSupport
module Testing
class ProxyTestResult
diff --git a/railties/bin/rails b/railties/bin/rails
index e743aa83f1..808df97429 100755
--- a/railties/bin/rails
+++ b/railties/bin/rails
@@ -19,6 +19,7 @@ end
ARGV << "--help" if ARGV.empty?
+require 'rails'
require 'rails/generators'
require 'rails/generators/rails/app/app_generator'
diff --git a/railties/builtin/rails_info/rails/info.rb b/railties/builtin/rails_info/rails/info.rb
index 48d89ad06a..c3784cff32 100644
--- a/railties/builtin/rails_info/rails/info.rb
+++ b/railties/builtin/rails_info/rails/info.rb
@@ -75,7 +75,7 @@ module Rails
protected
def rails_vendor_root
- @rails_vendor_root ||= "#{RAILS_ROOT}/vendor/rails"
+ @rails_vendor_root ||= "#{Rails.root}/vendor/rails"
end
def git_info
@@ -124,7 +124,7 @@ module Rails
# The application's location on the filesystem.
property 'Application root' do
- File.expand_path(RAILS_ROOT)
+ File.expand_path(Rails.root)
end
# The current Rails environment (development, test, or production).
diff --git a/railties/guides/source/2_2_release_notes.textile b/railties/guides/source/2_2_release_notes.textile
index f60af01050..15a7bdbd44 100644
--- a/railties/guides/source/2_2_release_notes.textile
+++ b/railties/guides/source/2_2_release_notes.textile
@@ -51,7 +51,7 @@ If you want to generate these guides locally, inside your application:
rake doc:guides
-This will put the guides inside +RAILS_ROOT/doc/guides+ and you may start surfing straight away by opening +RAILS_ROOT/doc/guides/index.html+ in your favourite browser.
+This will put the guides inside +Rails.root/doc/guides+ and you may start surfing straight away by opening +Rails.root/doc/guides/index.html+ in your favourite browser.
* Lead Contributors: "Rails Documentation Team":http://guides.rails.info/credits.html
* Major contributions from "Xavier Noria":http://advogato.org/person/fxn/diary.html and "Hongli Lai":http://izumi.plan99.net/blog/.
diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile
index 756caea5fe..46a28da8c4 100644
--- a/railties/guides/source/action_controller_overview.textile
+++ b/railties/guides/source/action_controller_overview.textile
@@ -653,7 +653,7 @@ class ClientsController < ApplicationController
# Stream a file that has already been generated and stored on disk.
def download_pdf
client = Client.find(params[:id])
- send_data("#{RAILS_ROOT}/files/clients/#{client.id}.pdf",
+ send_data("#{Rails.root}/files/clients/#{client.id}.pdf",
:filename => "#{client.name}.pdf",
:type => "application/pdf")
end
diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile
index d042458419..1a571358a1 100644
--- a/railties/guides/source/command_line.textile
+++ b/railties/guides/source/command_line.textile
@@ -424,10 +424,10 @@ INFO: For a good rundown on generators, see "Understanding Generators":http://wi
Generators are code that generates code. Let's experiment by building one. Our generator will generate a text file.
-The Rails generator by default looks in these places for available generators, where RAILS_ROOT is the root of your Rails application, like /home/foobar/commandsapp:
+The Rails generator by default looks in these places for available generators, where Rails.root is the root of your Rails application, like /home/foobar/commandsapp:
-* RAILS_ROOT/lib/generators
-* RAILS_ROOT/vendor/generators
+* Rails.root/lib/generators
+* Rails.root/vendor/generators
* Inside any plugin with a directory like "generators" or "rails_generators"
* ~/.rails/generators
* Inside any Gem you have installed with a name ending in "_generator"
@@ -465,7 +465,7 @@ We take whatever args are supplied, save them to an instance variable, and liter
* Check there's a *public* directory. You bet there is.
* Run the ERb template called "tutorial.erb".
-* Save it into "RAILS_ROOT/public/tutorial.txt".
+* Save it into "Rails.root/public/tutorial.txt".
* Pass in the arguments we saved through the +:assign+ parameter.
Next we'll build the template:
diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb
index 9ff8367807..5507f40c0d 100644
--- a/railties/lib/rails/backtrace_cleaner.rb
+++ b/railties/lib/rails/backtrace_cleaner.rb
@@ -18,7 +18,7 @@ module Rails
def initialize
super
- add_filter { |line| line.sub("#{RAILS_ROOT}/", '') }
+ add_filter { |line| line.sub("#{Rails.root}/", '') }
add_filter { |line| line.sub(ERB_METHOD_SIG, '') }
add_filter { |line| line.sub('./', '/') } # for tests
@@ -37,7 +37,7 @@ module Rails
add_filter { |line| line.sub(/(#{path})\/gems\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) \4')}
end
- vendor_gems_path = Rails::GemDependency.unpacked_path.sub("#{RAILS_ROOT}/",'')
+ vendor_gems_path = Rails::GemDependency.unpacked_path.sub("#{Rails.root}/",'')
add_filter { |line| line.sub(/(#{vendor_gems_path})\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) [v] \4')}
end
end
diff --git a/railties/lib/rails/commands/about.rb b/railties/lib/rails/commands/about.rb
index 9a05c47390..54c12a266f 100644
--- a/railties/lib/rails/commands/about.rb
+++ b/railties/lib/rails/commands/about.rb
@@ -1,4 +1,3 @@
-require "#{RAILS_ROOT}/config/application"
Rails.application.new
require 'rails/info'
puts Rails::Info
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 31448bdf1a..b977b7162f 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -12,7 +12,7 @@ OptionParser.new do |opt|
end
libs = " -r irb/completion"
-libs << %( -r "#{RAILS_ROOT}/config/environment")
+libs << %( -r "#{Rails.root}/config/environment")
libs << " -r rails/console_app"
libs << " -r rails/console_sandbox" if options[:sandbox]
libs << " -r rails/console_with_helpers"
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index e6f11a45db..4e699acf6b 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -25,7 +25,7 @@ OptionParser.new do |opt|
end
env = ARGV.first || ENV['RAILS_ENV'] || 'development'
-unless config = YAML::load(ERB.new(IO.read(RAILS_ROOT + "/config/database.yml")).result)[env]
+unless config = YAML::load(ERB.new(IO.read(Rails.root + "/config/database.yml")).result)[env]
abort "No database is configured for the environment '#{env}'"
end
diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb
index 860c03e01e..1270fdd033 100644
--- a/railties/lib/rails/commands/destroy.rb
+++ b/railties/lib/rails/commands/destroy.rb
@@ -1,5 +1,4 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'generators'))
-require "#{RAILS_ROOT}/config/application"
if ARGV.size == 0
Rails::Generators.help
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index c138cbc9bf..d29fa620fb 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -7,7 +7,7 @@ options = {
:Port => 3000,
:Host => "0.0.0.0",
:environment => (ENV['RAILS_ENV'] || "development").dup,
- :config => RAILS_ROOT + "/config.ru",
+ :config => Rails.root + "/config.ru",
:detach => false,
:debugger => false
}
@@ -46,7 +46,7 @@ puts "=> Rails #{Rails.version} application starting on http://#{options[:Host]}
if options[:detach]
Process.daemon
- pid = "#{RAILS_ROOT}/tmp/pids/server.pid"
+ pid = "#{Rails.root}/tmp/pids/server.pid"
File.open(pid, 'w'){ |f| f.write(Process.pid) }
at_exit { File.delete(pid) if File.exist?(pid) }
end
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index 3cb39245ff..ce9c899400 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -34,37 +34,25 @@ module Rails
def root
@root ||= begin
- if defined?(RAILS_ROOT)
- root = RAILS_ROOT
- else
- call_stack = caller.map { |p| p.split(':').first }
- root_path = call_stack.detect { |p| p !~ %r[railties/lib/rails] }
- root_path = File.dirname(root_path)
-
- while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/config.ru")
- parent = File.dirname(root_path)
- root_path = parent != root_path && parent
- end
-
- Object.class_eval("RAILS_ROOT = ''")
+ call_stack = caller.map { |p| p.split(':').first }
+ root_path = call_stack.detect { |p| p !~ %r[railties/lib/rails] }
+ root_path = File.dirname(root_path)
- root = File.exist?("#{root_path}/config.ru") ? root_path : Dir.pwd
+ while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/config.ru")
+ parent = File.dirname(root_path)
+ root_path = parent != root_path && parent
end
- root = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ?
- Pathname.new(root).expand_path.to_s :
- Pathname.new(root).realpath.to_s
+ root = File.exist?("#{root_path}/config.ru") ? root_path : Dir.pwd
- # TODO: Remove RAILS_ROOT
- RAILS_ROOT.replace(root)
- root
+ RUBY_PLATFORM =~ /(:?mswin|mingw)/ ?
+ Pathname.new(root).expand_path :
+ Pathname.new(root).realpath
end
end
def root=(root)
- Object.class_eval("RAILS_ROOT = ''") unless defined?(RAILS_ROOT)
- RAILS_ROOT.replace(root)
- @root = root
+ @root = Pathname.new(root).expand_path
end
def paths
diff --git a/railties/lib/rails/core.rb b/railties/lib/rails/core.rb
index 929c38bd22..e15661e2d7 100644
--- a/railties/lib/rails/core.rb
+++ b/railties/lib/rails/core.rb
@@ -6,7 +6,7 @@ module Rails
# TODO: w0t?
class << self
def application
- @@application
+ @@application ||= nil
end
def application=(application)
@@ -43,7 +43,7 @@ module Rails
end
def root
- Pathname.new(RAILS_ROOT) if defined?(RAILS_ROOT)
+ application && application.config.root
end
def env
diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb
new file mode 100644
index 0000000000..42bba151d6
--- /dev/null
+++ b/railties/lib/rails/deprecation.rb
@@ -0,0 +1,17 @@
+require "active_support/string_inquirer"
+require "active_support/deprecation"
+
+RAILS_ROOT = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do
+ def target
+ Rails.root
+ end
+
+ def replace(val)
+ puts OMG
+ end
+
+ def warn(callstack, called, args)
+ msg = "RAILS_ROOT is deprecated! Use Rails.root instead."
+ ActiveSupport::Deprecation.warn(msg, callstack)
+ end
+end).new
\ No newline at end of file
diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb
index 06d830ba24..804bd75e55 100644
--- a/railties/lib/rails/gem_dependency.rb
+++ b/railties/lib/rails/gem_dependency.rb
@@ -11,7 +11,7 @@ module Rails
attr_accessor :lib, :source, :dep
def self.unpacked_path
- @unpacked_path ||= File.join(RAILS_ROOT, 'vendor', 'gems')
+ @unpacked_path ||= File.join(Rails.root, 'vendor', 'gems')
end
@@framework_gems = {}
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 471eb45ee6..1010e51c94 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -92,8 +92,8 @@ module Rails
generator_path = File.join(spec.full_gem_path, "lib/generators")
paths << generator_path if File.exist?(generator_path)
end
- elsif defined?(RAILS_ROOT)
- paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "gems", "*", "lib", "generators")]
+ elsif defined?(Rails.root)
+ paths += Dir[File.join(Rails.root, "vendor", "gems", "gems", "*", "lib", "generators")]
end
paths
@@ -102,8 +102,8 @@ module Rails
# Load paths from plugin.
#
def self.plugins_generators_paths
- return [] unless defined?(RAILS_ROOT)
- Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")]
+ return [] unless Rails.root
+ Dir[File.join(Rails.root, "vendor", "plugins", "*", "lib", "generators")]
end
# Hold configured generators fallbacks. If a plugin developer wants a
@@ -143,7 +143,7 @@ module Rails
def self.load_paths
@load_paths ||= begin
paths = []
- paths << File.join(RAILS_ROOT, "lib", "generators") if defined?(RAILS_ROOT)
+ paths << File.join(Rails.root, "lib", "generators") if Rails.root
paths << File.join(Thor::Util.user_home, ".rails", "generators")
paths += self.plugins_generators_paths
paths += self.gems_generators_paths
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 720caa5b3f..7af99797ea 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -203,8 +203,8 @@ module Rails
super
base.source_root # Cache source root
- if defined?(RAILS_ROOT) && base.name !~ /Base$/
- path = File.expand_path(File.join(RAILS_ROOT, 'lib', 'templates'))
+ if Rails.root && base.name !~ /Base$/
+ path = File.expand_path(File.join(Rails.root, 'lib', 'templates'))
if base.name.include?('::')
base.source_paths << File.join(path, base.base_name, base.generator_name)
else
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index bff74a7786..cf786093ba 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -7,7 +7,7 @@ Rails::Initializer.run do |config|
# -- all .rb files in that directory are automatically loaded.
# Add additional load paths for your own custom dirs
- # config.load_paths += %W( #{RAILS_ROOT}/extras )
+ # config.load_paths += %W( #{root}/extras )
# Specify gems that this application depends on and have them installed with rake gems:install
# config.gem "bj"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
index 6e0e2279cd..12152a5d54 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
@@ -1,8 +1,6 @@
# Don't change this file!
# Configure your app in config/environment.rb and config/environments/*.rb
-RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
-
module Rails
# Mark the version of Rails that generated the boot.rb file. This is
# a temporary solution and will most likely be removed as Rails 3.0
@@ -70,7 +68,6 @@ module Rails
def load_initializer
require "rails"
install_gem_spec_stubs
- Rails::GemDependency.add_frozen_gem_path
end
def install_gem_spec_stubs
diff --git a/railties/lib/rails/initializer.rb b/railties/lib/rails/initializer.rb
index bb86f70da6..070a385052 100644
--- a/railties/lib/rails/initializer.rb
+++ b/railties/lib/rails/initializer.rb
@@ -9,6 +9,7 @@ require 'rails/rack'
require 'rails/paths'
require 'rails/core'
require 'rails/configuration'
+require 'rails/deprecation'
RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV)
diff --git a/railties/lib/rails/plugin/loader.rb b/railties/lib/rails/plugin/loader.rb
index 0d16cbd7c3..4808c6ad57 100644
--- a/railties/lib/rails/plugin/loader.rb
+++ b/railties/lib/rails/plugin/loader.rb
@@ -147,7 +147,7 @@ module Rails
end
def application_lib_index
- $LOAD_PATH.index(File.join(RAILS_ROOT, 'lib')) || 0
+ $LOAD_PATH.index(File.join(Rails.root, 'lib')) || 0
end
def enabled?(plugin)
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index aad965306c..148a3d4d30 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -20,5 +20,5 @@ end
# Load any custom rakefile extensions
# TODO: Don't hardcode these paths.
-Dir["#{RAILS_ROOT}/vendor/plugins/*/**/tasks/**/*.rake"].sort.each { |ext| load ext }
-Dir["#{RAILS_ROOT}/lib/tasks/**/*.rake"].sort.each { |ext| load ext }
+Dir["#{Rails.root}/vendor/plugins/*/**/tasks/**/*.rake"].sort.each { |ext| load ext }
+Dir["#{Rails.root}/lib/tasks/**/*.rake"].sort.each { |ext| load ext }
diff --git a/railties/lib/rails/tasks/databases.rake b/railties/lib/rails/tasks/databases.rake
index ed015e7a67..70b59625f8 100644
--- a/railties/lib/rails/tasks/databases.rake
+++ b/railties/lib/rails/tasks/databases.rake
@@ -283,7 +283,7 @@ namespace :db do
desc "Create a db/schema.rb file that can be portably used against any DB supported by AR"
task :dump => :environment do
require 'active_record/schema_dumper'
- File.open(ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb", "w") do |file|
+ File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
Rake::Task["db:schema:dump"].reenable
@@ -291,11 +291,11 @@ namespace :db do
desc "Load a schema.rb file into the database"
task :load => :environment do
- file = ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb"
+ file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
if File.exists?(file)
load(file)
else
- abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{RAILS_ROOT}/config/environment.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]}
+ abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/environment.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]}
end
end
end
@@ -307,7 +307,7 @@ namespace :db do
case abcs[RAILS_ENV]["adapter"]
when "mysql", "oci", "oracle"
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
- File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
+ File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
when "postgresql"
ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"]
ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"]
@@ -327,13 +327,13 @@ namespace :db do
when "firebird"
set_firebird_env(abcs[RAILS_ENV])
db_string = firebird_db_string(abcs[RAILS_ENV])
- sh "isql -a #{db_string} > #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql"
+ sh "isql -a #{db_string} > #{Rails.root}/db/#{RAILS_ENV}_structure.sql"
else
raise "Task not supported by '#{abcs["test"]["adapter"]}'"
end
if ActiveRecord::Base.connection.supports_migrations?
- File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
+ File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
end
end
end
@@ -356,28 +356,28 @@ namespace :db do
when "mysql"
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
- IO.readlines("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table|
+ IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table|
ActiveRecord::Base.connection.execute(table)
end
when "postgresql"
ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"]
ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"]
ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
- `psql -U "#{abcs["test"]["username"]}" -f #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}`
+ `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}`
when "sqlite", "sqlite3"
dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"]
- `#{abcs["test"]["adapter"]} #{dbfile} < #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql`
+ `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{RAILS_ENV}_structure.sql`
when "sqlserver"
`osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql`
when "oci", "oracle"
ActiveRecord::Base.establish_connection(:test)
- IO.readlines("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl|
+ IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl|
ActiveRecord::Base.connection.execute(ddl)
end
when "firebird"
set_firebird_env(abcs["test"])
db_string = firebird_db_string(abcs["test"])
- sh "isql -i #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql #{db_string}"
+ sh "isql -i #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{db_string}"
else
raise "Task not supported by '#{abcs["test"]["adapter"]}'"
end
@@ -446,7 +446,7 @@ def drop_database(config)
when /^sqlite/
require 'pathname'
path = Pathname.new(config['database'])
- file = path.absolute? ? path.to_s : File.join(RAILS_ROOT, path)
+ file = path.absolute? ? path.to_s : File.join(Rails.root, path)
FileUtils.rm(file)
when 'postgresql'
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 16dd0af44e..3d7e4b5453 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -86,7 +86,7 @@ namespace :rails do
template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://}
require 'generators'
- generator = Rails::Generators::App.new [ RAILS_ROOT ], {}, :destination_root => RAILS_ROOT
+ generator = Rails::Generators::App.new [ Rails.root ], {}, :destination_root => Rails.root
generator.apply template, :verbose => false
end
@@ -96,7 +96,7 @@ namespace :rails do
require 'rails/generators/rails/app/app_generator'
generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true },
- :destination_root => RAILS_ROOT
+ :destination_root => Rails.root
generator.invoke(method)
end
@@ -117,8 +117,8 @@ namespace :rails do
desc "Rename application.rb to application_controller.rb"
task :application_controller do
- old_style = RAILS_ROOT + '/app/controllers/application.rb'
- new_style = RAILS_ROOT + '/app/controllers/application_controller.rb'
+ old_style = Rails.root + '/app/controllers/application.rb'
+ new_style = Rails.root + '/app/controllers/application_controller.rb'
if File.exists?(old_style) && !File.exists?(new_style)
FileUtils.mv(old_style, new_style)
puts "#{old_style} has been renamed to #{new_style}, update your SCM as necessary"
diff --git a/railties/lib/rails/tasks/gems.rake b/railties/lib/rails/tasks/gems.rake
index f1c34c7cca..1903efd5f6 100644
--- a/railties/lib/rails/tasks/gems.rake
+++ b/railties/lib/rails/tasks/gems.rake
@@ -61,7 +61,7 @@ def current_gems
end
def frozen_gems(load_specs=true)
- Dir[File.join(RAILS_ROOT, 'vendor', 'gems', '*-*')].map do |gem_dir|
+ Dir[File.join(Rails.root, 'vendor', 'gems', '*-*')].map do |gem_dir|
Rails::GemDependency.from_directory_name(gem_dir, load_specs)
end
end
diff --git a/railties/lib/rails/tasks/misc.rake b/railties/lib/rails/tasks/misc.rake
index fb2fc31dc1..7f244ebaed 100644
--- a/railties/lib/rails/tasks/misc.rake
+++ b/railties/lib/rails/tasks/misc.rake
@@ -1,7 +1,7 @@
task :default => :test
task :environment do
$rails_rake_task = true
- require(File.join(RAILS_ROOT, 'config', 'environment'))
+ require(File.join(Rails.root, 'config', 'environment'))
end
task :rails_env do
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 8bd4475c7b..9f6c42945f 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -17,7 +17,7 @@ if defined?(ActiveRecord)
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
- self.fixture_path = "#{RAILS_ROOT}/test/fixtures/"
+ self.fixture_path = "#{Rails.root}/test/fixtures/"
self.use_instantiated_fixtures = false
self.use_transactional_fixtures = true
end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 551468b3e8..8010481609 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -14,15 +14,15 @@ $:.unshift File.dirname(__FILE__) + "/../builtin/rails_info"
require 'stringio'
require 'test/unit'
+require 'fileutils'
require 'active_support'
require 'active_support/core_ext/logger'
require 'active_support/test_case'
require 'action_controller'
+require 'rails'
-if defined?(RAILS_ROOT)
- RAILS_ROOT.replace File.dirname(__FILE__)
-else
- RAILS_ROOT = File.dirname(__FILE__)
-end
+Rails::Initializer.run do |config|
+ config.root = File.dirname(__FILE__)
+end
\ No newline at end of file
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index d90582d3db..0452208cae 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -12,7 +12,7 @@ module ApplicationTests
test "the application root is set correctly" do
require "#{app_path}/config/environment"
- assert_equal app_path, Rails.application.root
+ assert_equal Pathname.new(app_path), Rails.application.root
end
test "the application root can be set" do
@@ -22,7 +22,7 @@ module ApplicationTests
config.root = '#{app_path}/hello'
RUBY
require "#{app_path}/config/environment"
- assert_equal "#{app_path}/hello", Rails.application.root
+ assert_equal Pathname.new("#{app_path}/hello"), Rails.application.root
end
test "the application root is detected as where config.ru is located" do
@@ -31,7 +31,7 @@ module ApplicationTests
RUBY
FileUtils.mv "#{app_path}/config.ru", "#{app_path}/config/config.ru"
require "#{app_path}/config/environment"
- assert_equal "#{app_path}/config", Rails.application.root
+ assert_equal Pathname.new("#{app_path}/config"), Rails.application.root
end
test "the application root is Dir.pwd if there is no config.ru" do
@@ -42,7 +42,7 @@ module ApplicationTests
Dir.chdir("#{app_path}/app") do
require "#{app_path}/config/environment"
- assert_equal "#{app_path}/app", Rails.application.root
+ assert_equal Pathname.new("#{app_path}/app"), Rails.application.root
end
end
end
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index c664b61137..25c82578a3 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -37,6 +37,7 @@ module ApplicationTests
test "generators aliases and options on initialization" do
Rails::Initializer.run do |c|
+ c.frameworks = []
c.generators.rails :aliases => { :test_framework => "-w" }
c.generators.orm :datamapper
c.generators.test_framework :rspec
@@ -50,6 +51,7 @@ module ApplicationTests
test "generators no color on initialization" do
Rails::Initializer.run do |c|
+ c.frameworks = []
c.generators.colorize_logging = false
end
# Initialize the application
diff --git a/railties/test/application/initializer_test.rb b/railties/test/application/initializer_test.rb
index 76486d8f2c..805ff8b6be 100644
--- a/railties/test/application/initializer_test.rb
+++ b/railties/test/application/initializer_test.rb
@@ -10,33 +10,38 @@ module ApplicationTests
end
test "initializing an application initializes rails" do
- class MyApp < Rails::Application ; end
+ Rails::Initializer.run do |config|
+ config.root = app_path
+ end
if RUBY_VERSION < '1.9'
$KCODE = ''
- MyApp.new
+ Rails.application.new
assert_equal 'UTF8', $KCODE
else
Encoding.default_external = Encoding::US_ASCII
- MyApp.new
+ Rails.application.new
assert_equal Encoding::UTF_8, Encoding.default_external
end
end
test "initializing an application adds the application paths to the load path" do
- class MyApp < Rails::Application ; end
+ Rails::Initializer.run do |config|
+ config.root = app_path
+ end
- MyApp.new
+ Rails.application.new
assert $:.include?("#{app_path}/app/models")
end
test "adding an unknown framework raises an error" do
- class MyApp < Rails::Application
+ Rails::Initializer.run do |config|
+ config.root = app_path
config.frameworks << :action_foo
end
assert_raises RuntimeError do
- MyApp.new
+ Rails.application.new
end
end
@@ -49,6 +54,7 @@ module ApplicationTests
ZOO
Rails::Initializer.run do |config|
+ config.root = app_path
config.eager_load_paths = "#{app_path}/lib"
end
@@ -60,7 +66,7 @@ module ApplicationTests
test "load environment with global" do
app_file "config/environments/development.rb", "$initialize_test_set_from_env = 'success'"
assert_nil $initialize_test_set_from_env
- Rails::Initializer.run { }
+ Rails::Initializer.run { |config| config.root = app_path }
Rails.application.new
assert_equal "success", $initialize_test_set_from_env
end
@@ -68,6 +74,7 @@ module ApplicationTests
test "action_controller load paths set only if action controller in use" do
assert_nothing_raised NameError do
Rails::Initializer.run do |config|
+ config.root = app_path
config.frameworks = []
end
Rails.application.new
@@ -76,6 +83,7 @@ module ApplicationTests
test "action_pack is added to the load path if action_controller is required" do
Rails::Initializer.run do |config|
+ config.root = app_path
config.frameworks = [:action_controller]
end
Rails.application.new
@@ -85,6 +93,7 @@ module ApplicationTests
test "action_pack is added to the load path if action_view is required" do
Rails::Initializer.run do |config|
+ config.root = app_path
config.frameworks = [:action_view]
end
Rails.application.new
@@ -94,6 +103,7 @@ module ApplicationTests
test "after_initialize block works correctly" do
Rails::Initializer.run do |config|
+ config.root = app_path
config.after_initialize { $test_after_initialize_block1 = "success" }
config.after_initialize { $test_after_initialize_block2 = "congratulations" }
end
@@ -105,6 +115,7 @@ module ApplicationTests
test "after_initialize block works correctly when no block is passed" do
Rails::Initializer.run do |config|
+ config.root = app_path
config.after_initialize { $test_after_initialize_block1 = "success" }
config.after_initialize # don't pass a block, this is what we're testing!
config.after_initialize { $test_after_initialize_block2 = "congratulations" }
@@ -118,6 +129,7 @@ module ApplicationTests
# i18n
test "setting another default locale" do
Rails::Initializer.run do |config|
+ config.root = app_path
config.i18n.default_locale = :de
end
Rails.application.new
@@ -128,18 +140,21 @@ module ApplicationTests
test "no config locales dir present should return empty load path" do
FileUtils.rm_rf "#{app_path}/config/locales"
Rails::Initializer.run do |c|
+ c.root = app_path
assert_equal [], c.i18n.load_path
end
end
test "config locales dir present should be added to load path" do
Rails::Initializer.run do |c|
+ c.root = app_path
assert_equal ["#{app_path}/config/locales/en.yml"], c.i18n.load_path
end
end
test "config defaults should be added with config settings" do
Rails::Initializer.run do |c|
+ c.root = app_path
c.i18n.load_path << "my/other/locale.yml"
end
@@ -151,6 +166,7 @@ module ApplicationTests
# DB middleware
test "database middleware doesn't initialize when session store is not active_record" do
Rails::Initializer.run do |config|
+ config.root = app_path
config.action_controller.session_store = :cookie_store
end
Rails.application.new
@@ -160,6 +176,7 @@ module ApplicationTests
test "database middleware doesn't initialize when activerecord is not in frameworks" do
Rails::Initializer.run do |c|
+ c.root = app_path
c.frameworks = []
end
assert_equal [], Rails.application.config.middleware
@@ -167,6 +184,7 @@ module ApplicationTests
test "database middleware initializes when session store is active record" do
Rails::Initializer.run do |c|
+ c.root = app_path
c.action_controller.session_store = :active_record_store
end
Rails.application.new
@@ -178,6 +196,7 @@ module ApplicationTests
test "ensure database middleware doesn't use action_controller on initializing" do
Rails::Initializer.run do |c|
+ c.root = app_path
c.frameworks -= [:action_controller]
c.action_controller.session_store = :active_record_store
end
@@ -189,6 +208,7 @@ module ApplicationTests
# Pathview test
test "load view paths doesn't perform anything when action_view not in frameworks" do
Rails::Initializer.run do |c|
+ c.root = app_path
c.frameworks -= [:action_view]
end
Rails.application.new
@@ -197,12 +217,11 @@ module ApplicationTests
assert_equal [], ActionController::Base.view_paths
end
- # Rails root test
- test "Rails.root == RAILS_ROOT" do
- assert_equal RAILS_ROOT, Rails.root.to_s
- end
-
test "Rails.root should be a Pathname" do
+ Rails::Initializer.run do |c|
+ c.root = app_path
+ end
+ Rails.application.new
assert_instance_of Pathname, Rails.root
end
end
diff --git a/railties/test/application/plugins_test.rb b/railties/test/application/plugins_test.rb
index a4cf322139..6ea6d49460 100644
--- a/railties/test/application/plugins_test.rb
+++ b/railties/test/application/plugins_test.rb
@@ -17,7 +17,7 @@ module ApplicationTests
end
test "all plugins are loaded when registered plugin list is untouched" do
- Rails::Initializer.run { }
+ Rails::Initializer.run { |c| c.root = app_path }
Rails.application.new
assert_plugins [
:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby
@@ -25,19 +25,20 @@ module ApplicationTests
end
test "no plugins are loaded if the configuration has an empty plugin list" do
- Rails::Initializer.run { |c| c.plugins = [] }
+ Rails::Initializer.run { |c| c.root = app_path; c.plugins = [] }
assert_plugins [], Rails.application.config.loaded_plugins
end
test "only the specified plugins are located in the order listed" do
plugin_names = [:plugin_with_no_lib_dir, :acts_as_chunky_bacon]
- Rails::Initializer.run { |c| c.plugins = plugin_names }
+ Rails::Initializer.run { |c| c.root = app_path; c.plugins = plugin_names }
Rails.application.new
assert_plugins plugin_names, Rails.application.config.loaded_plugins
end
test "all plugins loaded after all" do
Rails::Initializer.run do |config|
+ config.root = app_path
config.plugins = [:stubby, :all, :acts_as_chunky_bacon]
end
Rails.application.new
@@ -47,6 +48,7 @@ module ApplicationTests
test "plugin names may be strings" do
plugin_names = ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir]
Rails::Initializer.run do |config|
+ config.root = app_path
config.plugins = ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir]
end
Rails.application.new
@@ -56,6 +58,7 @@ module ApplicationTests
test "all plugins loaded when all is used" do
Rails::Initializer.run do |config|
+ config.root = app_path
config.plugins = [:stubby, :acts_as_chunky_bacon, :all]
end
Rails.application.new
@@ -65,6 +68,7 @@ module ApplicationTests
test "all loaded plugins are added to the load paths" do
Rails::Initializer.run do |config|
+ config.root = app_path
config.plugins = [:stubby, :acts_as_chunky_bacon]
end
Rails.application.new
@@ -75,6 +79,7 @@ module ApplicationTests
test "registering a plugin name that does not exist raises a load error" do
Rails::Initializer.run do |config|
+ config.root = app_path
config.plugins = [:stubby, :acts_as_a_non_existant_plugin]
end
@@ -89,6 +94,7 @@ module ApplicationTests
begin
Rails::Initializer.run do |config|
+ config.root = app_path
config.plugins = [:stubby, :acts_as_chunky_bacon, :non_existant_plugin1, :non_existant_plugin2]
end
Rails.application.new
diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb
index c3e4f970fe..f9b9d3168d 100644
--- a/railties/test/backtrace_cleaner_test.rb
+++ b/railties/test/backtrace_cleaner_test.rb
@@ -1,6 +1,4 @@
require 'abstract_unit'
-
-require 'rails/initializer'
require 'rails/backtrace_cleaner'
if defined? Test::Unit::Util::BacktraceFilter
diff --git a/railties/test/boot_test.rb b/railties/test/boot_test.rb
index 1280d27ffe..46ef01f54c 100644
--- a/railties/test/boot_test.rb
+++ b/railties/test/boot_test.rb
@@ -57,7 +57,6 @@ class VendorBootTest < Test::Unit::TestCase
boot = VendorBoot.new
boot.expects(:require).with("rails")
boot.expects(:install_gem_spec_stubs)
- Rails::GemDependency.expects(:add_frozen_gem_path)
boot.load_initializer
end
end
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 7599bda8a2..ccf08c347c 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -1,24 +1,10 @@
-require 'test/unit'
-require 'fileutils'
-
-fixtures = File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures'))
-if defined?(RAILS_ROOT)
- RAILS_ROOT.replace fixtures
-else
- RAILS_ROOT = fixtures
-end
-
-$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activemodel/lib"
-$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activerecord/lib"
-$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../actionpack/lib"
-$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../lib"
# TODO: Fix this RAILS_ENV stuff
RAILS_ENV = 'test'
-require "rails/core"
-require 'rails/generators'
+require 'abstract_unit'
+Rails.application.config.root = File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures'))
+require 'rails/generators'
require 'rubygems'
-
require 'active_record'
require 'action_dispatch'
diff --git a/railties/test/initializer/initialize_i18n_test.rb b/railties/test/initializer/initialize_i18n_test.rb
index f952d06f94..5d921cb21a 100644
--- a/railties/test/initializer/initialize_i18n_test.rb
+++ b/railties/test/initializer/initialize_i18n_test.rb
@@ -12,6 +12,7 @@ module InitializerTests
# test_config_defaults_and_settings_should_be_added_to_i18n_defaults
test "i18n config defaults and settings should be added to i18n defaults" do
Rails::Initializer.run do |c|
+ c.root = app_path
c.i18n.load_path << "my/other/locale.yml"
end
Rails.application.new
@@ -34,6 +35,7 @@ module InitializerTests
app_file "vendor/plugins/engine/config/locales/en.yml", "hello:"
Rails::Initializer.run do |c|
+ c.root = app_path
c.i18n.load_path << "my/other/locale.yml"
end
Rails.application.new
diff --git a/railties/test/initializer/path_test.rb b/railties/test/initializer/path_test.rb
index ce8fc4b8b0..29acbdbd25 100644
--- a/railties/test/initializer/path_test.rb
+++ b/railties/test/initializer/path_test.rb
@@ -7,6 +7,7 @@ class PathsTest < Test::Unit::TestCase
build_app
boot_rails
Rails::Initializer.run do |config|
+ config.root = app_path
config.frameworks = [:action_controller, :action_view, :action_mailer, :active_record]
config.after_initialize do
ActionController::Base.session_store = nil
diff --git a/railties/test/initializer_test.rb b/railties/test/initializer_test.rb
deleted file mode 100644
index 80e774b7b7..0000000000
--- a/railties/test/initializer_test.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'abstract_unit'
-require 'rails/initializer'
-require 'rails/generators'
-
-require 'action_view'
-require 'action_mailer'
-require 'active_record'
-
-require 'plugin_test_helper'
-
-class RailsRootTest < Test::Unit::TestCase
- def test_rails_dot_root_equals_rails_root
- assert_equal RAILS_ROOT, Rails.root.to_s
- end
-
- def test_rails_dot_root_should_be_a_pathname
- assert_equal File.join(RAILS_ROOT, 'app', 'controllers'), Rails.root.join('app', 'controllers').to_s
- end
-end
-
--
cgit v1.2.3
From 2ca93403a259a8e6cead1c3a6eba675df9f1586a Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 26 Sep 2009 11:52:37 -0500
Subject: Ruby 1.9: qualify toplevel File reference since config.ru could be
eval'd in a different scope
Restores accidentally-reverted commit.
---
railties/lib/rails/generators/rails/app/templates/config.ru | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru
index 3702ad0466..e6e73ece4c 100644
--- a/railties/lib/rails/generators/rails/app/templates/config.ru
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru
@@ -1,5 +1,5 @@
# Require your environment file to bootstrap Rails
-require File.expand_path('../config/application', __FILE__)
+require ::File.expand_path('../config/application', __FILE__)
# Dispatch the request
run Rails.application.new
--
cgit v1.2.3
From 591eaf3b2c56ebb8a779c3ebbd1300a3f776f39a Mon Sep 17 00:00:00 2001
From: Carl Lerche
Date: Fri, 16 Oct 2009 16:01:46 -0700
Subject: Get apps generated with working again.
---
railties/lib/rails/commands/server.rb | 2 +-
.../generators/rails/app/templates/config/boot.rb | 93 ++++++++++------------
2 files changed, 44 insertions(+), 51 deletions(-)
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index d29fa620fb..29359e49a4 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -7,7 +7,7 @@ options = {
:Port => 3000,
:Host => "0.0.0.0",
:environment => (ENV['RAILS_ENV'] || "development").dup,
- :config => Rails.root + "/config.ru",
+ :config => "#{Rails.root}/config.ru",
:detach => false,
:debugger => false
}
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
index 12152a5d54..928b195d8d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
@@ -10,38 +10,33 @@ module Rails
class << self
def boot!
unless booted?
- preinitialize
- pick_boot.run
+ root = File.expand_path('../..', __FILE__)
+ booter = File.exist?("#{root}/vendor/rails") ? VendorBoot : GemBoot
+ booter.new(root).run
end
end
def booted?
defined? Rails::Initializer
end
-
- def pick_boot
- (vendor_rails? ? VendorBoot : GemBoot).new
- end
-
- def vendor_rails?
- File.exist?("#{RAILS_ROOT}/vendor/rails")
- end
-
- def preinitialize
- load(preinitializer_path) if File.exist?(preinitializer_path)
- end
-
- def preinitializer_path
- "#{RAILS_ROOT}/config/preinitializer.rb"
- end
end
class Boot
+ def initialize(root)
+ @root = root
+ end
+
def run
+ preinitialize
set_load_paths
load_initializer
end
+ def preinitialize
+ path = "#{@root}/config/preinitializer.rb"
+ load(path) if File.exist?(path)
+ end
+
def set_load_paths
%w(
actionmailer/lib
@@ -60,7 +55,7 @@ module Rails
end
def framework_root_path
- defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{RAILS_ROOT}/vendor/rails"
+ defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{@root}/vendor/rails"
end
end
@@ -85,13 +80,13 @@ module Rails
class GemBoot < Boot
def load_initializer
- self.class.load_rubygems
+ load_rubygems
load_rails_gem
require 'rails'
end
def load_rails_gem
- if version = self.class.gem_version
+ if version = gem_version
gem 'rails', version
else
gem 'rails'
@@ -101,43 +96,41 @@ module Rails
exit 1
end
- class << self
- def rubygems_version
- Gem::RubyGemsVersion rescue nil
- end
+ def rubygems_version
+ Gem::RubyGemsVersion rescue nil
+ end
- def gem_version
- if defined? RAILS_GEM_VERSION
- RAILS_GEM_VERSION
- elsif ENV.include?('RAILS_GEM_VERSION')
- ENV['RAILS_GEM_VERSION']
- else
- parse_gem_version(read_environment_rb)
- end
+ def gem_version
+ if defined? RAILS_GEM_VERSION
+ RAILS_GEM_VERSION
+ elsif ENV.include?('RAILS_GEM_VERSION')
+ ENV['RAILS_GEM_VERSION']
+ else
+ parse_gem_version(read_environment_rb)
end
+ end
- def load_rubygems
- min_version = '1.3.2'
- require 'rubygems'
- unless rubygems_version >= min_version
- $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
- exit 1
- end
-
- rescue LoadError
- $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
+ def load_rubygems
+ min_version = '1.3.2'
+ require 'rubygems'
+ unless rubygems_version >= min_version
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
exit 1
end
- def parse_gem_version(text)
- $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
- end
+ rescue LoadError
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
+ exit 1
+ end
- private
- def read_environment_rb
- File.read("#{RAILS_ROOT}/config/environment.rb")
- end
+ def parse_gem_version(text)
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
end
+
+ private
+ def read_environment_rb
+ File.read("#{@root}/config/environment.rb")
+ end
end
end
--
cgit v1.2.3
From c296b33ef1ca9f2d576104e3938bd21afba02377 Mon Sep 17 00:00:00 2001
From: Carl Lerche
Date: Fri, 16 Oct 2009 16:31:05 -0700
Subject: Make the railties tests pass
---
railties/test/boot_test.rb | 171 -------------------------------
railties/test/isolation/abstract_unit.rb | 2 +-
2 files changed, 1 insertion(+), 172 deletions(-)
delete mode 100644 railties/test/boot_test.rb
diff --git a/railties/test/boot_test.rb b/railties/test/boot_test.rb
deleted file mode 100644
index 46ef01f54c..0000000000
--- a/railties/test/boot_test.rb
+++ /dev/null
@@ -1,171 +0,0 @@
-require 'abstract_unit'
-require 'rails/initializer'
-require "#{File.dirname(__FILE__)}/../lib/rails/generators/rails/app/templates/config/boot"
-require 'rails/gem_dependency'
-
-class BootTest < Test::Unit::TestCase
- def test_boot_returns_if_booted
- Rails.expects(:booted?).returns(true)
- Rails.expects(:pick_boot).never
- assert_nil Rails.boot!
- end
-
- def test_boot_preinitializes_then_picks_and_runs_if_not_booted
- Rails.expects(:booted?).returns(false)
- Rails.expects(:preinitialize)
- Rails.expects(:pick_boot).returns(mock(:run => 'result'))
- assert_equal 'result', Rails.boot!
- end
-
- def test_preinitialize_does_not_raise_exception_if_preinitializer_file_does_not_exist
- Rails.stubs(:preinitializer_path).returns('/there/is/no/such/file')
-
- assert_nothing_raised { Rails.preinitialize }
- end
-
- def test_load_preinitializer_loads_preinitializer_file
- Rails.stubs(:preinitializer_path).returns("#{File.dirname(__FILE__)}/fixtures/environment_with_constant.rb")
-
- assert_nil $initialize_test_set_from_env
- Rails.preinitialize
- assert_equal "success", $initialize_test_set_from_env
- ensure
- $initialize_test_set_from_env = nil
- end
-
- def test_boot_vendor_rails_by_default
- Rails.expects(:booted?).returns(false)
- Rails.expects(:preinitialize)
- File.expects(:exist?).with("#{RAILS_ROOT}/vendor/rails").returns(true)
- Rails::VendorBoot.any_instance.expects(:run).returns('result')
- assert_equal 'result', Rails.boot!
- end
-
- def test_boot_gem_rails_otherwise
- Rails.expects(:booted?).returns(false)
- Rails.expects(:preinitialize)
- File.expects(:exist?).with("#{RAILS_ROOT}/vendor/rails").returns(false)
- Rails::GemBoot.any_instance.expects(:run).returns('result')
- assert_equal 'result', Rails.boot!
- end
-end
-
-class VendorBootTest < Test::Unit::TestCase
- include Rails
-
- def test_load_initializer_requires_from_vendor_rails
- boot = VendorBoot.new
- boot.expects(:require).with("rails")
- boot.expects(:install_gem_spec_stubs)
- boot.load_initializer
- end
-end
-
-class GemBootTest < Test::Unit::TestCase
- include Rails
-
- def test_load_initializer_loads_rubygems_and_the_rails_gem
- boot = GemBoot.new
- GemBoot.expects(:load_rubygems)
- boot.expects(:load_rails_gem)
- boot.expects(:require).with('rails')
- boot.load_initializer
- end
-
- def test_load_rubygems_exits_with_error_if_missing
- GemBoot.expects(:require).with('rubygems').raises(LoadError, 'missing rubygems')
- STDERR.expects(:puts)
- GemBoot.expects(:exit).with(1)
- GemBoot.load_rubygems
- end
-
- def test_load_rubygems_exits_with_error_if_too_old
- GemBoot.stubs(:rubygems_version).returns('0.0.1')
- GemBoot.expects(:require).with('rubygems').returns(true)
- STDERR.expects(:puts)
- GemBoot.expects(:exit).with(1)
- GemBoot.load_rubygems
- end
-
- def test_load_rails_gem_activates_specific_gem_if_version_given
- GemBoot.stubs(:gem_version).returns('0.0.1')
-
- boot = GemBoot.new
- boot.expects(:gem).with('rails', '0.0.1')
- boot.load_rails_gem
- end
-
- def test_load_rails_gem_activates_latest_gem_if_no_version_given
- GemBoot.stubs(:gem_version).returns(nil)
-
- boot = GemBoot.new
- boot.expects(:gem).with('rails')
- boot.load_rails_gem
- end
-
- def test_load_rails_gem_exits_with_error_if_missing
- GemBoot.stubs(:gem_version).returns('0.0.1')
-
- boot = GemBoot.new
- boot.expects(:gem).with('rails', '0.0.1').raises(Gem::LoadError, 'missing rails 0.0.1 gem')
- STDERR.expects(:puts)
- boot.expects(:exit).with(1)
- boot.load_rails_gem
- end
-end
-
-class ParseGemVersionTest < Test::Unit::TestCase
- def test_should_return_nil_if_no_lines_are_passed
- assert_equal nil, parse('')
- assert_equal nil, parse(nil)
- end
-
- def test_should_accept_either_single_or_double_quotes
- assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3'")
- assert_equal "1.2.3", parse('RAILS_GEM_VERSION = "1.2.3"')
- end
-
- def test_should_return_nil_if_no_lines_match
- assert_equal nil, parse('nothing matches on this line\nor on this line')
- end
-
- def test_should_parse_with_no_leading_space
- assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION")
- assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3'")
- end
-
- def test_should_parse_with_any_number_of_leading_spaces
- assert_equal nil, parse([])
- assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION")
- assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION")
- assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3'")
- assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3'")
- end
-
- def test_should_ignore_unrelated_comments
- assert_equal "1.2.3", parse("# comment\nRAILS_GEM_VERSION = '1.2.3'\n# comment")
- end
-
- def test_should_ignore_commented_version_lines
- assert_equal "1.2.3", parse("#RAILS_GEM_VERSION = '9.8.7'\nRAILS_GEM_VERSION = '1.2.3'")
- assert_equal "1.2.3", parse("# RAILS_GEM_VERSION = '9.8.7'\nRAILS_GEM_VERSION = '1.2.3'")
- assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3'\n# RAILS_GEM_VERSION = '9.8.7'")
- end
-
- def test_should_allow_advanced_rubygems_version_specifications
- # See http://rubygems.org/read/chapter/16
- assert_equal "=1.2.3", parse("RAILS_GEM_VERSION = '=1.2.3'") # equal sign
- assert_equal "= 1.2.3", parse("RAILS_GEM_VERSION = '= 1.2.3'") # with space
- assert_equal "!=1.2.3", parse("RAILS_GEM_VERSION = '!=1.2.3'") # not equal
- assert_equal ">1.2.3", parse("RAILS_GEM_VERSION = '>1.2.3'") # greater than
- assert_equal "<1.2.3", parse("RAILS_GEM_VERSION = '<1.2.3'") # less than
- assert_equal ">=1.2.3", parse("RAILS_GEM_VERSION = '>=1.2.3'") # greater than or equal
- assert_equal "<=1.2.3", parse("RAILS_GEM_VERSION = '<=1.2.3'") # less than or equal
- assert_equal "~>1.2.3.0", parse("RAILS_GEM_VERSION = '~>1.2.3.0'") # approximately greater than
- end
-
- private
- def parse(text)
- Rails::GemBoot.parse_gem_version(text)
- end
-end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 5bc878b3be..750ec5d319 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -120,7 +120,7 @@ module TestHelpers
Initializer = 'lol'
require "#{app_path}/config/boot"
remove_const(:Initializer)
- booter = VendorBoot.new
+ booter = VendorBoot.new('#{app_path}')
booter.run
end
RUBY
--
cgit v1.2.3
From 3971d972c304272dbbd88d5d5df14b5739e801a2 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 18:42:55 -0500
Subject: Expand paths in i18n initializer tests
---
railties/test/initializer/initialize_i18n_test.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/railties/test/initializer/initialize_i18n_test.rb b/railties/test/initializer/initialize_i18n_test.rb
index 5d921cb21a..92ac1312bf 100644
--- a/railties/test/initializer/initialize_i18n_test.rb
+++ b/railties/test/initializer/initialize_i18n_test.rb
@@ -25,7 +25,7 @@ module InitializerTests
#{RAILS_FRAMEWORK_ROOT}/actionpack/lib/action_view/locale/en.yml
#{RAILS_FRAMEWORK_ROOT}/railties/tmp/app/config/locales/en.yml
my/other/locale.yml
- ), I18n.load_path
+ ).map { |path| File.expand_path(path) }, I18n.load_path.map { |path| File.expand_path(path) }
end
test "i18n finds locale files in engines" do
@@ -49,7 +49,7 @@ module InitializerTests
#{app_path}/config/locales/en.yml
my/other/locale.yml
#{app_path}/vendor/plugins/engine/config/locales/en.yml
- ), I18n.load_path
+ ).map { |path| File.expand_path(path) }, I18n.load_path.map { |path| File.expand_path(path) }
end
end
end
\ No newline at end of file
--
cgit v1.2.3
From 92bfc693f2293fe5d459a6edb4216594cd5a7eb2 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 18:46:34 -0500
Subject: Rackup file should require environment
---
railties/lib/rails/generators/rails/app/templates/config.ru | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru
index e6e73ece4c..f854029193 100644
--- a/railties/lib/rails/generators/rails/app/templates/config.ru
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru
@@ -1,5 +1,5 @@
# Require your environment file to bootstrap Rails
-require ::File.expand_path('../config/application', __FILE__)
+require ::File.expand_path('../config/environment', __FILE__)
# Dispatch the request
run Rails.application.new
--
cgit v1.2.3
From d8594026962704b6b51e188a29406fbd22bb31ce Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 18:58:35 -0500
Subject: Use Rails.initialize! where we just want to run the initializers and
aren't concerned about the config
---
railties/lib/rails/application.rb | 5 ++++
railties/lib/rails/commands/about.rb | 2 +-
railties/lib/rails/commands/generate.rb | 2 +-
.../lib/rails/commands/performance/benchmarker.rb | 2 +-
.../lib/rails/commands/performance/profiler.rb | 2 +-
railties/lib/rails/commands/runner.rb | 2 +-
railties/lib/rails/core.rb | 4 +++
.../rails/app/templates/config/environment.rb | 2 +-
railties/test/application/generators_test.rb | 4 +--
railties/test/application/initializer_test.rb | 34 +++++++++++-----------
railties/test/application/plugins_test.rb | 18 ++++++------
.../test/initializer/check_ruby_version_test.rb | 4 +--
railties/test/initializer/initialize_i18n_test.rb | 4 +--
railties/test/initializer/path_test.rb | 2 +-
14 files changed, 48 insertions(+), 39 deletions(-)
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index fe2dc3769e..621f1c3878 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -3,6 +3,11 @@ module Rails
extend Initializable
class << self
+ # Stub out App initialize
+ def initialize!
+ new
+ end
+
def config
@config ||= Configuration.new
end
diff --git a/railties/lib/rails/commands/about.rb b/railties/lib/rails/commands/about.rb
index 54c12a266f..6fdbc08027 100644
--- a/railties/lib/rails/commands/about.rb
+++ b/railties/lib/rails/commands/about.rb
@@ -1,3 +1,3 @@
-Rails.application.new
+Rails.initialize!
require 'rails/info'
puts Rails::Info
diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb
index cfa6a51d94..22aefa803a 100755
--- a/railties/lib/rails/commands/generate.rb
+++ b/railties/lib/rails/commands/generate.rb
@@ -1,5 +1,5 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'generators'))
-Rails.application.new
+Rails.initialize!
if ARGV.size == 0
Rails::Generators.help
diff --git a/railties/lib/rails/commands/performance/benchmarker.rb b/railties/lib/rails/commands/performance/benchmarker.rb
index 5039c5408c..5420b57311 100644
--- a/railties/lib/rails/commands/performance/benchmarker.rb
+++ b/railties/lib/rails/commands/performance/benchmarker.rb
@@ -12,7 +12,7 @@ end
require 'benchmark'
include Benchmark
-Rails.application.new
+Rails.initialize!
# Don't include compilation in the benchmark
ARGV.each { |expression| eval(expression) }
diff --git a/railties/lib/rails/commands/performance/profiler.rb b/railties/lib/rails/commands/performance/profiler.rb
index 7274e2dfb7..30346dc0e7 100644
--- a/railties/lib/rails/commands/performance/profiler.rb
+++ b/railties/lib/rails/commands/performance/profiler.rb
@@ -5,7 +5,7 @@ end
# Keep the expensive require out of the profile.
$stderr.puts 'Loading Rails...'
-Rails.application.new # Initialize the application
+Rails.initialize! # Initialize the application
# Define a method to profile.
if ARGV[1] and ARGV[1].to_i > 1
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index d24f36dd63..84d72f9fa9 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -36,7 +36,7 @@ ARGV.delete(code_or_file)
ENV["RAILS_ENV"] = options[:environment]
RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV)
-Rails.application.new
+Rails.initialize!
begin
if code_or_file.nil?
diff --git a/railties/lib/rails/core.rb b/railties/lib/rails/core.rb
index e15661e2d7..6e7f912e6d 100644
--- a/railties/lib/rails/core.rb
+++ b/railties/lib/rails/core.rb
@@ -18,6 +18,10 @@ module Rails
application.configuration
end
+ def initialize!
+ application.initialize!
+ end
+
def initialized?
@initialized || false
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
index fcf4eddb00..3bb0f2619e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
@@ -6,4 +6,4 @@
# Load the rails application
require File.expand_path(File.join(File.dirname(__FILE__), 'application'))
# Initialize the rails application
-Rails.application.new
+Rails.initialize!
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index 25c82578a3..03fecffdd0 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -43,7 +43,7 @@ module ApplicationTests
c.generators.test_framework :rspec
end
# Initialize the application
- Rails.application.new
+ Rails.initialize!
assert_equal :rspec, Rails::Generators.options[:rails][:test_framework]
assert_equal "-w", Rails::Generators.aliases[:rails][:test_framework]
@@ -55,7 +55,7 @@ module ApplicationTests
c.generators.colorize_logging = false
end
# Initialize the application
- Rails.application.new
+ Rails.initialize!
assert_equal Thor::Base.shell, Thor::Shell::Basic
end
diff --git a/railties/test/application/initializer_test.rb b/railties/test/application/initializer_test.rb
index 805ff8b6be..c2e64374d0 100644
--- a/railties/test/application/initializer_test.rb
+++ b/railties/test/application/initializer_test.rb
@@ -16,11 +16,11 @@ module ApplicationTests
if RUBY_VERSION < '1.9'
$KCODE = ''
- Rails.application.new
+ Rails.initialize!
assert_equal 'UTF8', $KCODE
else
Encoding.default_external = Encoding::US_ASCII
- Rails.application.new
+ Rails.initialize!
assert_equal Encoding::UTF_8, Encoding.default_external
end
end
@@ -30,7 +30,7 @@ module ApplicationTests
config.root = app_path
end
- Rails.application.new
+ Rails.initialize!
assert $:.include?("#{app_path}/app/models")
end
@@ -41,7 +41,7 @@ module ApplicationTests
end
assert_raises RuntimeError do
- Rails.application.new
+ Rails.initialize!
end
end
@@ -58,7 +58,7 @@ module ApplicationTests
config.eager_load_paths = "#{app_path}/lib"
end
- Rails.application.new
+ Rails.initialize!
assert Zoo
end
@@ -67,7 +67,7 @@ module ApplicationTests
app_file "config/environments/development.rb", "$initialize_test_set_from_env = 'success'"
assert_nil $initialize_test_set_from_env
Rails::Initializer.run { |config| config.root = app_path }
- Rails.application.new
+ Rails.initialize!
assert_equal "success", $initialize_test_set_from_env
end
@@ -77,7 +77,7 @@ module ApplicationTests
config.root = app_path
config.frameworks = []
end
- Rails.application.new
+ Rails.initialize!
end
end
@@ -86,7 +86,7 @@ module ApplicationTests
config.root = app_path
config.frameworks = [:action_controller]
end
- Rails.application.new
+ Rails.initialize!
assert $:.include?("#{framework_path}/actionpack/lib")
end
@@ -96,7 +96,7 @@ module ApplicationTests
config.root = app_path
config.frameworks = [:action_view]
end
- Rails.application.new
+ Rails.initialize!
assert $:.include?("#{framework_path}/actionpack/lib")
end
@@ -107,7 +107,7 @@ module ApplicationTests
config.after_initialize { $test_after_initialize_block1 = "success" }
config.after_initialize { $test_after_initialize_block2 = "congratulations" }
end
- Rails.application.new
+ Rails.initialize!
assert_equal "success", $test_after_initialize_block1
assert_equal "congratulations", $test_after_initialize_block2
@@ -120,7 +120,7 @@ module ApplicationTests
config.after_initialize # don't pass a block, this is what we're testing!
config.after_initialize { $test_after_initialize_block2 = "congratulations" }
end
- Rails.application.new
+ Rails.initialize!
assert_equal "success", $test_after_initialize_block1
assert_equal "congratulations", $test_after_initialize_block2
@@ -132,7 +132,7 @@ module ApplicationTests
config.root = app_path
config.i18n.default_locale = :de
end
- Rails.application.new
+ Rails.initialize!
assert_equal :de, I18n.default_locale
end
@@ -169,7 +169,7 @@ module ApplicationTests
config.root = app_path
config.action_controller.session_store = :cookie_store
end
- Rails.application.new
+ Rails.initialize!
assert !Rails.application.config.middleware.include?(ActiveRecord::SessionStore)
end
@@ -187,7 +187,7 @@ module ApplicationTests
c.root = app_path
c.action_controller.session_store = :active_record_store
end
- Rails.application.new
+ Rails.initialize!
expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore]
middleware = Rails.application.config.middleware.map { |m| m.klass }
@@ -200,7 +200,7 @@ module ApplicationTests
c.frameworks -= [:action_controller]
c.action_controller.session_store = :active_record_store
end
- Rails.application.new
+ Rails.initialize!
assert !Rails.application.config.middleware.include?(ActiveRecord::SessionStore)
end
@@ -211,7 +211,7 @@ module ApplicationTests
c.root = app_path
c.frameworks -= [:action_view]
end
- Rails.application.new
+ Rails.initialize!
assert_equal nil, ActionMailer::Base.template_root
assert_equal [], ActionController::Base.view_paths
@@ -221,7 +221,7 @@ module ApplicationTests
Rails::Initializer.run do |c|
c.root = app_path
end
- Rails.application.new
+ Rails.initialize!
assert_instance_of Pathname, Rails.root
end
end
diff --git a/railties/test/application/plugins_test.rb b/railties/test/application/plugins_test.rb
index 6ea6d49460..0926ed106b 100644
--- a/railties/test/application/plugins_test.rb
+++ b/railties/test/application/plugins_test.rb
@@ -18,7 +18,7 @@ module ApplicationTests
test "all plugins are loaded when registered plugin list is untouched" do
Rails::Initializer.run { |c| c.root = app_path }
- Rails.application.new
+ Rails.initialize!
assert_plugins [
:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby
], Rails.application.config.loaded_plugins, @failure_tip
@@ -32,7 +32,7 @@ module ApplicationTests
test "only the specified plugins are located in the order listed" do
plugin_names = [:plugin_with_no_lib_dir, :acts_as_chunky_bacon]
Rails::Initializer.run { |c| c.root = app_path; c.plugins = plugin_names }
- Rails.application.new
+ Rails.initialize!
assert_plugins plugin_names, Rails.application.config.loaded_plugins
end
@@ -41,7 +41,7 @@ module ApplicationTests
config.root = app_path
config.plugins = [:stubby, :all, :acts_as_chunky_bacon]
end
- Rails.application.new
+ Rails.initialize!
assert_plugins [:stubby, :a, :engine, :gemlike, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], Rails.application.config.loaded_plugins, @failure_tip
end
@@ -51,7 +51,7 @@ module ApplicationTests
config.root = app_path
config.plugins = ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir]
end
- Rails.application.new
+ Rails.initialize!
assert_plugins plugin_names, Rails.application.config.loaded_plugins, @failure_tip
end
@@ -61,8 +61,8 @@ module ApplicationTests
config.root = app_path
config.plugins = [:stubby, :acts_as_chunky_bacon, :all]
end
- Rails.application.new
-
+ Rails.initialize!
+
assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :engine, :gemlike, :plugin_with_no_lib_dir], Rails.application.config.loaded_plugins, @failure_tip
end
@@ -71,7 +71,7 @@ module ApplicationTests
config.root = app_path
config.plugins = [:stubby, :acts_as_chunky_bacon]
end
- Rails.application.new
+ Rails.initialize!
assert $LOAD_PATH.include?("#{app_path}/vendor/plugins/default/stubby/lib")
assert $LOAD_PATH.include?("#{app_path}/vendor/plugins/default/acts/acts_as_chunky_bacon/lib")
@@ -84,7 +84,7 @@ module ApplicationTests
end
assert_raise(LoadError) do
- Rails.application.new
+ Rails.initialize!
end
end
@@ -97,7 +97,7 @@ module ApplicationTests
config.root = app_path
config.plugins = [:stubby, :acts_as_chunky_bacon, :non_existant_plugin1, :non_existant_plugin2]
end
- Rails.application.new
+ Rails.initialize!
flunk "Expected a LoadError but did not get one"
rescue LoadError => e
assert_plugins valid_plugins, Rails.application.config.loaded_plugins, @failure_tip
diff --git a/railties/test/initializer/check_ruby_version_test.rb b/railties/test/initializer/check_ruby_version_test.rb
index 6c32f5635b..0c725311ad 100644
--- a/railties/test/initializer/check_ruby_version_test.rb
+++ b/railties/test/initializer/check_ruby_version_test.rb
@@ -42,7 +42,7 @@ module InitializerTests
set_ruby_version(version)
assert_nothing_raised "It appears that rails does not boot" do
Rails::Initializer.run { |c| c.frameworks = [] }
- Rails.application.new
+ Rails.initialize!
end
end
@@ -51,7 +51,7 @@ module InitializerTests
$stderr = File.open("/dev/null", "w")
assert_raises(SystemExit) do
Rails::Initializer.run { |c| c.frameworks = [] }
- Rails.application.new
+ Rails.initialize!
end
end
end
diff --git a/railties/test/initializer/initialize_i18n_test.rb b/railties/test/initializer/initialize_i18n_test.rb
index 92ac1312bf..04b44cedd0 100644
--- a/railties/test/initializer/initialize_i18n_test.rb
+++ b/railties/test/initializer/initialize_i18n_test.rb
@@ -15,7 +15,7 @@ module InitializerTests
c.root = app_path
c.i18n.load_path << "my/other/locale.yml"
end
- Rails.application.new
+ Rails.initialize!
#{RAILS_FRAMEWORK_ROOT}/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml
assert_equal %W(
@@ -38,7 +38,7 @@ module InitializerTests
c.root = app_path
c.i18n.load_path << "my/other/locale.yml"
end
- Rails.application.new
+ Rails.initialize!
#{RAILS_FRAMEWORK_ROOT}/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml
assert_equal %W(
diff --git a/railties/test/initializer/path_test.rb b/railties/test/initializer/path_test.rb
index 29acbdbd25..9c36bb2000 100644
--- a/railties/test/initializer/path_test.rb
+++ b/railties/test/initializer/path_test.rb
@@ -13,7 +13,7 @@ class PathsTest < Test::Unit::TestCase
ActionController::Base.session_store = nil
end
end
- Rails.application.new
+ Rails.initialize!
@paths = Rails.application.config.paths
end
--
cgit v1.2.3
From c1261b5484c10930be9f737abfc164f9ba072629 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 19:04:28 -0500
Subject: Use Rails.application where we want a valid rack app
---
actionpack/lib/action_controller/dispatch/dispatcher.rb | 2 +-
actionpack/lib/action_dispatch/testing/integration.rb | 2 +-
railties/lib/rails/generators/rails/app/templates/config.ru | 2 +-
railties/test/application/load_test.rb | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/actionpack/lib/action_controller/dispatch/dispatcher.rb b/actionpack/lib/action_controller/dispatch/dispatcher.rb
index 008fb54715..e04da42637 100644
--- a/actionpack/lib/action_controller/dispatch/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatch/dispatcher.rb
@@ -50,7 +50,7 @@ module ActionController
def new
# DEPRECATE Rails application fallback
- Rails.application.new
+ Rails.application
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 58ebe94a5b..40d6f97b2a 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -490,7 +490,7 @@ module ActionDispatch
def self.app
# DEPRECATE Rails application fallback
# This should be set by the initializer
- @@app || (defined?(Rails.application) && Rails.application.new) || nil
+ @@app || (defined?(Rails.application) && Rails.application) || nil
end
def self.app=(app)
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru
index f854029193..509a0da5b7 100644
--- a/railties/lib/rails/generators/rails/app/templates/config.ru
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru
@@ -2,4 +2,4 @@
require ::File.expand_path('../config/environment', __FILE__)
# Dispatch the request
-run Rails.application.new
+run Rails.application
diff --git a/railties/test/application/load_test.rb b/railties/test/application/load_test.rb
index 5c3d35fb16..3da51c4355 100644
--- a/railties/test/application/load_test.rb
+++ b/railties/test/application/load_test.rb
@@ -40,7 +40,7 @@ module ApplicationTests
test "Rails.application is available after config.ru has been racked up" do
rackup
- assert Rails.application.new < Rails::Application
+ assert Rails.application < Rails::Application
end
# Passenger still uses AC::Dispatcher, so we need to
--
cgit v1.2.3
From 4cebd41d9e9764b29a5673a298a71b0c530b2bf6 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 19:08:09 -0500
Subject: Fix Rails root in sqlite adapter
---
.../lib/active_record/connection_adapters/sqlite_adapter.rb | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 5a49fc2d2f..c9c2892ba4 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -11,11 +11,11 @@ module ActiveRecord
raise ArgumentError, "No database file specified. Missing argument: database"
end
- # Allow database path relative to RAILS_ROOT, but only if
+ # Allow database path relative to Rails.root, but only if
# the database path is not the special path that tells
# Sqlite to build a database only in memory.
- if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database]
- config[:database] = File.expand_path(config[:database], RAILS_ROOT)
+ if Object.const_defined?(:Rails) && ':memory:' != config[:database]
+ config[:database] = File.expand_path(config[:database], Rails.root)
end
end
end
--
cgit v1.2.3
From c40f79f3304ecfb45699a540da8a5e68c2a2af44 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 19:24:19 -0500
Subject: script/about should require environment
---
railties/lib/rails/commands/about.rb | 1 -
railties/lib/rails/generators/rails/app/templates/script/about.tt | 2 +-
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/railties/lib/rails/commands/about.rb b/railties/lib/rails/commands/about.rb
index 6fdbc08027..d4c30bbeb2 100644
--- a/railties/lib/rails/commands/about.rb
+++ b/railties/lib/rails/commands/about.rb
@@ -1,3 +1,2 @@
-Rails.initialize!
require 'rails/info'
puts Rails::Info
diff --git a/railties/lib/rails/generators/rails/app/templates/script/about.tt b/railties/lib/rails/generators/rails/app/templates/script/about.tt
index 90a320297e..7639d4040f 100755
--- a/railties/lib/rails/generators/rails/app/templates/script/about.tt
+++ b/railties/lib/rails/generators/rails/app/templates/script/about.tt
@@ -1,4 +1,4 @@
<%= shebang %>
-require File.expand_path('../../config/application', __FILE__)
+require File.expand_path('../../config/environment', __FILE__)
$LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info"
require 'rails/commands/about'
--
cgit v1.2.3
From a22394295cd4ed30a4c2b63e9d59ca3f2505bb00 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 19:25:21 -0500
Subject: Ignore log folder created by tests
---
railties/.gitignore | 1 +
1 file changed, 1 insertion(+)
create mode 100644 railties/.gitignore
diff --git a/railties/.gitignore b/railties/.gitignore
new file mode 100644
index 0000000000..80dd262d2f
--- /dev/null
+++ b/railties/.gitignore
@@ -0,0 +1 @@
+log/
--
cgit v1.2.3
From 7e9e370e031c20e36a52c04a20dbbd7b4fe4672f Mon Sep 17 00:00:00 2001
From: Yehuda Katz
Date: Fri, 16 Oct 2009 17:28:29 -0700
Subject: Make encodings work with Erubis and 1.9 again
---
actionpack/lib/action_view/template/handlers/erb.rb | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 8993dd2b35..88aeb4b053 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -42,9 +42,11 @@ module ActionView
self.erubis_implementation = Erubis
def compile(template)
- magic = $1 if template.source =~ /\A(<%#.*coding[:=]\s*(\S+)\s*-?%>)/
- erb = "#{magic}<% __in_erb_template=true %>#{template.source}"
- self.class.erubis_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src
+ source = template.source.gsub(/\A(<%(#.*coding[:=]\s*(\S+)\s*)-?%>)\s*\n?/, '')
+ erb = "<% __in_erb_template=true %>#{source}"
+ result = self.class.erubis_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src
+ result = "#{$2}\n#{result}" if $2
+ result
end
end
end
--
cgit v1.2.3
From 7635c9f4c0f53714b46166224d604f0558b8525f Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 19:27:03 -0500
Subject: script/generate should require environment
---
railties/lib/rails/commands/generate.rb | 1 -
railties/lib/rails/generators/rails/app/templates/script/generate.tt | 2 +-
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb
index 22aefa803a..d91dcf9788 100755
--- a/railties/lib/rails/commands/generate.rb
+++ b/railties/lib/rails/commands/generate.rb
@@ -1,5 +1,4 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'generators'))
-Rails.initialize!
if ARGV.size == 0
Rails::Generators.help
diff --git a/railties/lib/rails/generators/rails/app/templates/script/generate.tt b/railties/lib/rails/generators/rails/app/templates/script/generate.tt
index 2a9d219531..26f029c6a6 100755
--- a/railties/lib/rails/generators/rails/app/templates/script/generate.tt
+++ b/railties/lib/rails/generators/rails/app/templates/script/generate.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.expand_path('../../config/application', __FILE__)
+require File.expand_path('../../config/environment', __FILE__)
require 'rails/commands/generate'
--
cgit v1.2.3
From 2f7217c76a68b048b15d2b8d821a2aa569ef924a Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 19:28:56 -0500
Subject: script/performance/benchmarker should require environment
---
railties/lib/rails/commands/performance/benchmarker.rb | 3 +--
.../generators/rails/app/templates/script/performance/benchmarker.tt | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/railties/lib/rails/commands/performance/benchmarker.rb b/railties/lib/rails/commands/performance/benchmarker.rb
index 5420b57311..dfba4bf034 100644
--- a/railties/lib/rails/commands/performance/benchmarker.rb
+++ b/railties/lib/rails/commands/performance/benchmarker.rb
@@ -12,7 +12,6 @@ end
require 'benchmark'
include Benchmark
-Rails.initialize!
# Don't include compilation in the benchmark
ARGV.each { |expression| eval(expression) }
@@ -21,4 +20,4 @@ bm(6) do |x|
ARGV.each_with_index do |expression, idx|
x.report("##{idx + 1}") { N.times { eval(expression) } }
end
-end
+end
diff --git a/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker.tt b/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker.tt
index 90aca9b4e9..9ebc4c92fc 100755
--- a/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker.tt
+++ b/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.expand_path('../../../config/application', __FILE__)
+require File.expand_path('../../../config/environment', __FILE__)
require 'rails/commands/performance/benchmarker'
--
cgit v1.2.3
From 8f2d8f452823791bb2dfbef4044f6cdac9a60a2e Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 19:30:05 -0500
Subject: script/performance/profiler should require environment
---
railties/lib/rails/commands/performance/profiler.rb | 4 ----
.../generators/rails/app/templates/script/performance/profiler.tt | 2 +-
2 files changed, 1 insertion(+), 5 deletions(-)
diff --git a/railties/lib/rails/commands/performance/profiler.rb b/railties/lib/rails/commands/performance/profiler.rb
index 30346dc0e7..aaa075018c 100644
--- a/railties/lib/rails/commands/performance/profiler.rb
+++ b/railties/lib/rails/commands/performance/profiler.rb
@@ -3,10 +3,6 @@ if ARGV.empty?
exit(1)
end
-# Keep the expensive require out of the profile.
-$stderr.puts 'Loading Rails...'
-Rails.initialize! # Initialize the application
-
# Define a method to profile.
if ARGV[1] and ARGV[1].to_i > 1
eval "def profile_me() #{ARGV[1]}.times { #{ARGV[0]} } end"
diff --git a/railties/lib/rails/generators/rails/app/templates/script/performance/profiler.tt b/railties/lib/rails/generators/rails/app/templates/script/performance/profiler.tt
index 39569b4e70..5f4c763f9d 100755
--- a/railties/lib/rails/generators/rails/app/templates/script/performance/profiler.tt
+++ b/railties/lib/rails/generators/rails/app/templates/script/performance/profiler.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.expand_path('../../../config/application', __FILE__)
+require File.expand_path('../../../config/environment', __FILE__)
require 'rails/commands/performance/profiler'
--
cgit v1.2.3
From f1640a165373f0e52ed36642479f3772318dab1f Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 19:31:32 -0500
Subject: script/runner should require environment
---
railties/lib/rails/commands/runner.rb | 2 --
railties/lib/rails/generators/rails/app/templates/script/runner.tt | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 84d72f9fa9..0246348c77 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -36,8 +36,6 @@ ARGV.delete(code_or_file)
ENV["RAILS_ENV"] = options[:environment]
RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV)
-Rails.initialize!
-
begin
if code_or_file.nil?
$stderr.puts "Run '#{$0} -h' for help."
diff --git a/railties/lib/rails/generators/rails/app/templates/script/runner.tt b/railties/lib/rails/generators/rails/app/templates/script/runner.tt
index 872ec07a1e..5fa1883694 100755
--- a/railties/lib/rails/generators/rails/app/templates/script/runner.tt
+++ b/railties/lib/rails/generators/rails/app/templates/script/runner.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.expand_path('../../config/application', __FILE__)
+require File.expand_path('../../../config/environment', __FILE__)
require 'rails/commands/runner'
--
cgit v1.2.3
From c7bf1058d3c536407a4c7683489e8f12c98f28ba Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 19:33:02 -0500
Subject: Fix incorrect path in script/runner template
---
railties/lib/rails/generators/rails/app/templates/script/runner.tt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/railties/lib/rails/generators/rails/app/templates/script/runner.tt b/railties/lib/rails/generators/rails/app/templates/script/runner.tt
index 5fa1883694..34ad7c18eb 100755
--- a/railties/lib/rails/generators/rails/app/templates/script/runner.tt
+++ b/railties/lib/rails/generators/rails/app/templates/script/runner.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.expand_path('../../../config/environment', __FILE__)
+require File.expand_path('../../config/environment', __FILE__)
require 'rails/commands/runner'
--
cgit v1.2.3
From 028911ad00e7f7733064a80393a1548401bba7af Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Fri, 16 Oct 2009 17:39:12 -0700
Subject: Use rails/rack
---
actionpack/Gemfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/actionpack/Gemfile b/actionpack/Gemfile
index 506458365f..c236169b20 100644
--- a/actionpack/Gemfile
+++ b/actionpack/Gemfile
@@ -2,7 +2,7 @@ rails_root = Pathname.new(File.dirname(__FILE__)).join("..")
Gem.sources.each { |uri| source uri }
-gem "rack", "1.0.1", :git => "git://github.com/rack/rack.git", :branch => "rack-1.0"
+gem "rack", "1.0.1", :git => "git://github.com/rails/rack.git", :branch => "rack-1.0"
gem "rack-test", "~> 0.5.0"
gem "activesupport", "3.0.pre", :vendored_at => rails_root.join("activesupport")
gem "activemodel", "3.0.pre", :vendored_at => rails_root.join("activemodel")
--
cgit v1.2.3
From 7593e8b8b56b030a321b0ab071155a44512685c1 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 19:40:08 -0500
Subject: Rakefile doesn't need initializers to be ran
---
railties/lib/rails/generators/rails/app/templates/Rakefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile
index 1dce9863f2..2450a927f0 100755
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile
@@ -1,7 +1,7 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
-require File.expand_path(File.join(File.dirname(__FILE__), 'config', 'environment'))
+require File.expand_path(File.join(File.dirname(__FILE__), 'config', 'application'))
require 'rake'
require 'rake/testtask'
--
cgit v1.2.3
From 29ba9c030704fb76dc2debbfecbb10fd70233d18 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 19:40:59 -0500
Subject: Use Rails.root in statistics rake task
---
railties/lib/rails/tasks/statistics.rake | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index 2dcc7bdf9d..40f8c1034a 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -1,14 +1,13 @@
STATS_DIRECTORIES = [
%w(Controllers app/controllers),
- %w(Helpers app/helpers),
+ %w(Helpers app/helpers),
%w(Models app/models),
%w(Libraries lib/),
%w(APIs app/apis),
%w(Integration\ tests test/integration),
%w(Functional\ tests test/functional),
%w(Unit\ tests test/unit)
-
-].collect { |name, dir| [ name, "#{RAILS_ROOT}/#{dir}" ] }.select { |name, dir| File.directory?(dir) }
+].collect { |name, dir| [ name, "#{Rails.root}/#{dir}" ] }.select { |name, dir| File.directory?(dir) }
desc "Report code statistics (KLOCs, etc) from the application"
task :stats do
--
cgit v1.2.3
From e1fdc8bba3e427435927685e77937b3a478aa416 Mon Sep 17 00:00:00 2001
From: Carl Lerche
Date: Fri, 16 Oct 2009 18:10:12 -0700
Subject: Remove config.gem in favor of using the bundler. This makes
config/boot.rb obsolete.
The bundler library is at: http://github.com/wycats/bundler/ and is a rubygem.
---
railties/lib/rails/application.rb | 16 --
railties/lib/rails/backtrace_cleaner.rb | 4 -
railties/lib/rails/gem_builder.rb | 21 --
railties/lib/rails/gem_dependency.rb | 311 ---------------------
railties/lib/rails/generators.rb | 2 -
.../rails/generators/rails/app/app_generator.rb | 4 -
.../rails/generators/rails/app/templates/Gemfile | 17 +-
.../rails/app/templates/config/application.rb | 11 +-
.../generators/rails/app/templates/config/boot.rb | 138 ---------
railties/lib/rails/initializer.rb | 1 -
railties/lib/rails/tasks.rb | 1 -
railties/lib/rails/tasks/framework.rake | 5 -
railties/lib/rails/tasks/gems.rake | 78 ------
railties/lib/rails/vendor_gem_source_index.rb | 140 ----------
railties/test/application/configuration_test.rb | 1 -
railties/test/application/generators_test.rb | 1 +
railties/test/application/initializer_test.rb | 1 +
railties/test/application/notifications_test.rb | 2 +-
railties/test/application/plugins_test.rb | 1 +
railties/test/backtrace_cleaner_test.rb | 6 -
railties/test/generators_test.rb | 3 +-
.../test/initializer/check_ruby_version_test.rb | 1 +
railties/test/initializer/initialize_i18n_test.rb | 1 +
railties/test/initializer/path_test.rb | 1 +
railties/test/isolation/abstract_unit.rb | 23 +-
25 files changed, 45 insertions(+), 745 deletions(-)
delete mode 100644 railties/lib/rails/gem_builder.rb
delete mode 100644 railties/lib/rails/gem_dependency.rb
delete mode 100644 railties/lib/rails/generators/rails/app/templates/config/boot.rb
delete mode 100644 railties/lib/rails/tasks/gems.rake
delete mode 100644 railties/lib/rails/vendor_gem_source_index.rb
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 621f1c3878..521e1cafb6 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -57,13 +57,6 @@ module Rails
$LOAD_PATH.uniq!
end
- # Bail if boot.rb is outdated
- initializer :freak_out_if_boot_rb_is_outdated do
- unless defined?(Rails::BOOTSTRAP_VERSION)
- abort %{Your config/boot.rb is outdated: Run "rake rails:update".}
- end
- end
-
# Requires all frameworks specified by the Configuration#frameworks
# list. By default, all frameworks (Active Record, Active Support,
# Action Pack, Action Mailer, and Active Resource) are loaded.
@@ -133,15 +126,6 @@ module Rails
end
end
- initializer :add_gem_load_paths do
- require 'rails/gem_dependency'
- Rails::GemDependency.add_frozen_gem_path
- unless config.gems.empty?
- require "rubygems"
- config.gems.each { |gem| gem.add_load_paths }
- end
- end
-
# Preload all frameworks specified by the Configuration#frameworks.
# Used by Passenger to ensure everything's loaded before forking and
# to avoid autoload race conditions in JRuby.
diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb
index 5507f40c0d..2f5632c3e4 100644
--- a/railties/lib/rails/backtrace_cleaner.rb
+++ b/railties/lib/rails/backtrace_cleaner.rb
@@ -1,5 +1,4 @@
require 'active_support/backtrace_cleaner'
-require 'rails/gem_dependency'
module Rails
class BacktraceCleaner < ActiveSupport::BacktraceCleaner
@@ -36,9 +35,6 @@ module Rails
# http://gist.github.com/30430
add_filter { |line| line.sub(/(#{path})\/gems\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) \4')}
end
-
- vendor_gems_path = Rails::GemDependency.unpacked_path.sub("#{Rails.root}/",'')
- add_filter { |line| line.sub(/(#{vendor_gems_path})\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) [v] \4')}
end
end
diff --git a/railties/lib/rails/gem_builder.rb b/railties/lib/rails/gem_builder.rb
deleted file mode 100644
index 79c61cc034..0000000000
--- a/railties/lib/rails/gem_builder.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'rubygems'
-require 'rubygems/installer'
-
-module Rails
-
- # this class hijacks the functionality of Gem::Installer by overloading its
- # initializer to only provide the information needed by
- # Gem::Installer#build_extensions (which happens to be what we have)
- class GemBuilder < Gem::Installer
-
- def initialize(spec, gem_dir)
- @spec = spec
- @gem_dir = gem_dir
- end
-
- # silence the underlying builder
- def say(message)
- end
-
- end
-end
diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb
deleted file mode 100644
index 804bd75e55..0000000000
--- a/railties/lib/rails/gem_dependency.rb
+++ /dev/null
@@ -1,311 +0,0 @@
-require 'rails/vendor_gem_source_index'
-
-module Gem
- def self.source_index=(index)
- @@source_index = index
- end
-end
-
-module Rails
- class GemDependency < Gem::Dependency
- attr_accessor :lib, :source, :dep
-
- def self.unpacked_path
- @unpacked_path ||= File.join(Rails.root, 'vendor', 'gems')
- end
-
- @@framework_gems = {}
-
- def self.add_frozen_gem_path
- @@paths_loaded ||= begin
- source_index = Rails::VendorGemSourceIndex.new(Gem.source_index)
- Gem.clear_paths
- Gem.source_index = source_index
- # loaded before us - we can't change them, so mark them
- Gem.loaded_specs.each do |name, spec|
- @@framework_gems[name] = spec
- end
- true
- end
- end
-
- def self.from_directory_name(directory_name, load_spec=true)
- directory_name_parts = File.basename(directory_name).split('-')
- name = directory_name_parts[0..-2].join('-')
- version = directory_name_parts.last
- result = self.new(name, :version => version)
- spec_filename = File.join(directory_name, '.specification')
- if load_spec
- raise "Missing specification file in #{File.dirname(spec_filename)}. Perhaps you need to do a 'rake gems:refresh_specs'?" unless File.exists?(spec_filename)
- spec = YAML::load_file(spec_filename)
- result.specification = spec
- end
- result
- rescue ArgumentError => e
- raise "Unable to determine gem name and version from '#{directory_name}'"
- end
-
- def initialize(name, options = {})
- require 'rubygems' unless Object.const_defined?(:Gem)
-
- if options[:requirement]
- req = options[:requirement]
- elsif options[:version]
- req = Gem::Requirement.create(options[:version])
- else
- req = Gem::Requirement.default
- end
-
- @lib = options[:lib]
- @source = options[:source]
- @loaded = @frozen = @load_paths_added = false
-
- super(name, req)
- end
-
- def add_load_paths
- self.class.add_frozen_gem_path
- return if @loaded || @load_paths_added
- if framework_gem?
- @load_paths_added = @loaded = @frozen = true
- return
- end
- gem self
- @spec = Gem.loaded_specs[name]
- @frozen = @spec.loaded_from.include?(self.class.unpacked_path) if @spec
- @load_paths_added = true
- rescue Gem::LoadError
- end
-
- def dependencies
- return [] if framework_gem?
- return [] unless installed?
- specification.dependencies.reject do |dependency|
- dependency.type == :development
- end.map do |dependency|
- GemDependency.new(dependency.name, :requirement => dependency.version_requirements)
- end
- end
-
- def specification
- # code repeated from Gem.activate. Find a matching spec, or the currently loaded version.
- # error out if loaded version and requested version are incompatible.
- @spec ||= begin
- matches = Gem.source_index.search(self)
- matches << @@framework_gems[name] if framework_gem?
- if Gem.loaded_specs[name] then
- # This gem is already loaded. If the currently loaded gem is not in the
- # list of candidate gems, then we have a version conflict.
- existing_spec = Gem.loaded_specs[name]
- unless matches.any? { |spec| spec.version == existing_spec.version } then
- raise Gem::Exception,
- "can't activate #{@dep}, already activated #{existing_spec.full_name}"
- end
- # we're stuck with it, so change to match
- version_requirements = Gem::Requirement.create("=#{existing_spec.version}")
- existing_spec
- else
- # new load
- matches.last
- end
- end
- end
-
- def specification=(s)
- @spec = s
- end
-
- def requirement
- r = version_requirements
- (r == Gem::Requirement.default) ? nil : r
- end
-
- def built?
- return false unless frozen?
-
- if vendor_gem?
- specification.extensions.each do |ext|
- makefile = File.join(unpacked_gem_directory, File.dirname(ext), 'Makefile')
- return false unless File.exists?(makefile)
- end
- end
-
- true
- end
-
- def framework_gem?
- @@framework_gems.has_key?(name)
- end
-
- def frozen?
- @frozen ||= vendor_rails? || vendor_gem?
- end
-
- def installed?
- Gem.loaded_specs.keys.include?(name)
- end
-
- def load_paths_added?
- # always try to add load paths - even if a gem is loaded, it may not
- # be a compatible version (ie random_gem 0.4 is loaded and a later spec
- # needs >= 0.5 - gem 'random_gem' will catch this and error out)
- @load_paths_added
- end
-
- def loaded?
- @loaded ||= begin
- if vendor_rails?
- true
- elsif specification.nil?
- false
- else
- # check if the gem is loaded by inspecting $"
- # specification.files lists all the files contained in the gem
- gem_files = specification.files
- # select only the files contained in require_paths - typically in bin and lib
- require_paths_regexp = Regexp.new("^(#{specification.require_paths*'|'})/")
- gem_lib_files = gem_files.select { |f| require_paths_regexp.match(f) }
- # chop the leading directory off - a typical file might be in
- # lib/gem_name/file_name.rb, but it will be 'require'd as gem_name/file_name.rb
- gem_lib_files.map! { |f| f.split('/', 2)[1] }
- # if any of the files from the above list appear in $", the gem is assumed to
- # have been loaded
- !(gem_lib_files & $").empty?
- end
- end
- end
-
- def vendor_rails?
- Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty?
- end
-
- def vendor_gem?
- specification && File.exists?(unpacked_gem_directory)
- end
-
- def build(options={})
- require 'rails/gem_builder'
- return if specification.nil?
- if options[:force] || !built?
- return unless File.exists?(unpacked_specification_filename)
- spec = YAML::load_file(unpacked_specification_filename)
- Rails::GemBuilder.new(spec, unpacked_gem_directory).build_extensions
- puts "Built gem: '#{unpacked_gem_directory}'"
- end
- dependencies.each { |dep| dep.build(options) }
- end
-
- def install
- unless installed?
- cmd = "#{gem_command} #{install_command.join(' ')}"
- puts cmd
- puts %x(#{cmd})
- end
- end
-
- def load
- return if @loaded || @load_paths_added == false
- require(@lib || name) unless @lib == false
- @loaded = true
- rescue LoadError
- puts $!.to_s
- $!.backtrace.each { |b| puts b }
- end
-
- def refresh
- Rails::VendorGemSourceIndex.silence_spec_warnings = true
- real_gems = Gem.source_index.installed_source_index
- exact_dep = Gem::Dependency.new(name, "= #{specification.version}")
- matches = real_gems.search(exact_dep)
- installed_spec = matches.first
- if frozen?
- if installed_spec
- # we have a real copy
- # get a fresh spec - matches should only have one element
- # note that there is no reliable method to check that the loaded
- # spec is the same as the copy from real_gems - Gem.activate changes
- # some of the fields
- real_spec = Gem::Specification.load(matches.first.loaded_from)
- write_specification(real_spec)
- puts "Reloaded specification for #{name} from installed gems."
- else
- # the gem isn't installed locally - write out our current specs
- write_specification(specification)
- puts "Gem #{name} not loaded locally - writing out current spec."
- end
- else
- if framework_gem?
- puts "Gem directory for #{name} not found - check if it's loading before rails."
- else
- puts "Something bad is going on - gem directory not found for #{name}."
- end
- end
- end
-
- def unpack(options={})
- unless frozen? || framework_gem?
- FileUtils.mkdir_p unpack_base
- Dir.chdir unpack_base do
- Gem::GemRunner.new.run(unpack_command)
- end
- # Gem.activate changes the spec - get the original
- real_spec = Gem::Specification.load(specification.loaded_from)
- write_specification(real_spec)
- end
- dependencies.each { |dep| dep.unpack(options) } if options[:recursive]
- end
-
- def write_specification(spec)
- # copy the gem's specification into GEMDIR/.specification so that
- # we can access information about the gem on deployment systems
- # without having the gem installed
- File.open(unpacked_specification_filename, 'w') do |file|
- file.puts spec.to_yaml
- end
- end
-
- def ==(other)
- self.name == other.name && self.requirement == other.requirement
- end
- alias_method :"eql?", :"=="
-
- private
-
- def gem_command
- case RUBY_PLATFORM
- when /win32/
- 'gem.bat'
- when /java/
- 'jruby -S gem'
- else
- 'gem'
- end
- end
-
- def install_command
- cmd = %w(install) << name
- cmd << "--version" << %("#{requirement.to_s}") if requirement
- cmd << "--source" << @source if @source
- cmd
- end
-
- def unpack_command
- cmd = %w(unpack) << name
- cmd << "--version" << "= "+specification.version.to_s if requirement
- cmd
- end
-
- def unpack_base
- Rails::GemDependency.unpacked_path
- end
-
- def unpacked_gem_directory
- File.join(unpack_base, specification.full_name)
- end
-
- def unpacked_specification_filename
- File.join(unpacked_gem_directory, '.specification')
- end
-
- end
-end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 1010e51c94..d1bdbd5253 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -92,8 +92,6 @@ module Rails
generator_path = File.join(spec.full_gem_path, "lib/generators")
paths << generator_path if File.exist?(generator_path)
end
- elsif defined?(Rails.root)
- paths += Dir[File.join(Rails.root, "vendor", "gems", "gems", "*", "lib", "generators")]
end
paths
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 49b13cae2b..20843cd720 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -75,10 +75,6 @@ module Rails::Generators
end
end
- def create_boot_file
- copy_file "config/boot.rb"
- end
-
def create_activerecord_files
return if options[:skip_activerecord]
template "config/databases/#{options[:database]}.yml", "config/database.yml"
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index bcbaa432b7..abe8c1556c 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -5,6 +5,17 @@ gem "rails", "<%= Rails::VERSION::STRING %>"
# Bundling edge rails:
# gem "rails", "<%= Rails::VERSION::STRING %>", :git => "git://github.com/rails/rails.git"
-# You can list more dependencies here
-# gem "nokogiri"
-# gem "merb" # ;)
\ No newline at end of file
+# Specify gemcutter as a gem source
+# source "http://gemcutter.org"
+
+# Specify gems that this application depends on and have them installed with rake gems:install
+# gem "bj"
+# gem "hpricot", "0.6"
+# gem "sqlite3-ruby", :require_as => "sqlite3"
+# gem "aws-s3", :require_as => "aws/s3"
+
+# Specify gems that should only be required in certain environments
+# gem "rspec", :only => :test
+# only :test do
+# gem "webrat"
+# end
\ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index cf786093ba..451dba86fb 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -1,5 +1,12 @@
-# Bootstrap the Rails environment, frameworks, and default configuration
-require File.expand_path(File.join(File.dirname(__FILE__), 'boot'))
+begin
+ # Use Bundler
+ require File.expand_path("../../vendor/gems/environment", __FILE__)
+rescue LoadError
+ # Use Rubygems
+ require 'rubygems'
+end
+
+require 'rails'
Rails::Initializer.run do |config|
# Settings in config/environments/* take precedence over those specified here.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
deleted file mode 100644
index 928b195d8d..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ /dev/null
@@ -1,138 +0,0 @@
-# Don't change this file!
-# Configure your app in config/environment.rb and config/environments/*.rb
-
-module Rails
- # Mark the version of Rails that generated the boot.rb file. This is
- # a temporary solution and will most likely be removed as Rails 3.0
- # comes closer.
- BOOTSTRAP_VERSION = "3.0"
-
- class << self
- def boot!
- unless booted?
- root = File.expand_path('../..', __FILE__)
- booter = File.exist?("#{root}/vendor/rails") ? VendorBoot : GemBoot
- booter.new(root).run
- end
- end
-
- def booted?
- defined? Rails::Initializer
- end
- end
-
- class Boot
- def initialize(root)
- @root = root
- end
-
- def run
- preinitialize
- set_load_paths
- load_initializer
- end
-
- def preinitialize
- path = "#{@root}/config/preinitializer.rb"
- load(path) if File.exist?(path)
- end
-
- def set_load_paths
- %w(
- actionmailer/lib
- actionpack/lib
- activemodel/lib
- activerecord/lib
- activeresource/lib
- activesupport/lib
- railties/lib
- railties
- ).reverse_each do |path|
- path = "#{framework_root_path}/#{path}"
- $LOAD_PATH.unshift(path) if File.directory?(path)
- $LOAD_PATH.uniq!
- end
- end
-
- def framework_root_path
- defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{@root}/vendor/rails"
- end
- end
-
- class VendorBoot < Boot
- def load_initializer
- require "rails"
- install_gem_spec_stubs
- end
-
- def install_gem_spec_stubs
- begin; require "rubygems"; rescue LoadError; return; end
-
- %w(rails activesupport activerecord actionpack actionmailer activeresource).each do |stub|
- Gem.loaded_specs[stub] ||= Gem::Specification.new do |s|
- s.name = stub
- s.version = Rails::VERSION::STRING
- s.loaded_from = ""
- end
- end
- end
- end
-
- class GemBoot < Boot
- def load_initializer
- load_rubygems
- load_rails_gem
- require 'rails'
- end
-
- def load_rails_gem
- if version = gem_version
- gem 'rails', version
- else
- gem 'rails'
- end
- rescue Gem::LoadError => load_error
- $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
- exit 1
- end
-
- def rubygems_version
- Gem::RubyGemsVersion rescue nil
- end
-
- def gem_version
- if defined? RAILS_GEM_VERSION
- RAILS_GEM_VERSION
- elsif ENV.include?('RAILS_GEM_VERSION')
- ENV['RAILS_GEM_VERSION']
- else
- parse_gem_version(read_environment_rb)
- end
- end
-
- def load_rubygems
- min_version = '1.3.2'
- require 'rubygems'
- unless rubygems_version >= min_version
- $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
- exit 1
- end
-
- rescue LoadError
- $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
- exit 1
- end
-
- def parse_gem_version(text)
- $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
- end
-
- private
- def read_environment_rb
- File.read("#{@root}/config/environment.rb")
- end
- end
-end
-
-# All that for this:
-Rails.boot!
diff --git a/railties/lib/rails/initializer.rb b/railties/lib/rails/initializer.rb
index 070a385052..2ad1e52746 100644
--- a/railties/lib/rails/initializer.rb
+++ b/railties/lib/rails/initializer.rb
@@ -4,7 +4,6 @@ require 'rails/initializable'
require 'rails/application'
require 'rails/railties_path'
require 'rails/version'
-require 'rails/gem_dependency'
require 'rails/rack'
require 'rails/paths'
require 'rails/core'
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index 148a3d4d30..82113a297c 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -6,7 +6,6 @@ $VERBOSE = nil
databases
documentation
framework
- gems
log
middleware
misc
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 3d7e4b5453..1611d1d94d 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -100,11 +100,6 @@ namespace :rails do
generator.invoke(method)
end
- desc "Update config/boot.rb from your current rails install"
- task :configs do
- invoke_from_app_generator :create_boot_file
- end
-
desc "Update Prototype javascripts from your current rails install"
task :javascripts do
invoke_from_app_generator :create_prototype_files
diff --git a/railties/lib/rails/tasks/gems.rake b/railties/lib/rails/tasks/gems.rake
deleted file mode 100644
index 1903efd5f6..0000000000
--- a/railties/lib/rails/tasks/gems.rake
+++ /dev/null
@@ -1,78 +0,0 @@
-desc "List the gems that this rails application depends on"
-task :gems => 'gems:base' do
- Rails.configuration.gems.each do |gem|
- print_gem_status(gem)
- end
- puts
- puts "I = Installed"
- puts "F = Frozen"
- puts "R = Framework (loaded before rails starts)"
-end
-
-namespace :gems do
- task :base do
- $gems_rake_task = true
- require 'rubygems'
- require 'rubygems/gem_runner'
- Rake::Task[:environment].invoke
- end
-
- desc "Build any native extensions for unpacked gems"
- task :build do
- $gems_build_rake_task = true
- frozen_gems.each { |gem| gem.build }
- end
-
- namespace :build do
- desc "Force the build of all gems"
- task :force do
- $gems_build_rake_task = true
- frozen_gems.each { |gem| gem.build(:force => true) }
- end
- end
-
- desc "Installs all required gems."
- task :install => :base do
- current_gems.each { |gem| gem.install }
- end
-
- desc "Unpacks all required gems into vendor/gems."
- task :unpack => :install do
- current_gems.each { |gem| gem.unpack }
- end
-
- namespace :unpack do
- desc "Unpacks all required gems and their dependencies into vendor/gems."
- task :dependencies => :install do
- current_gems.each { |gem| gem.unpack(:recursive => true) }
- end
- end
-
- desc "Regenerate gem specifications in correct format."
- task :refresh_specs do
- frozen_gems(false).each { |gem| gem.refresh }
- end
-end
-
-def current_gems
- gems = Rails.configuration.gems
- gems = gems.select { |gem| gem.name == ENV['GEM'] } unless ENV['GEM'].blank?
- gems
-end
-
-def frozen_gems(load_specs=true)
- Dir[File.join(Rails.root, 'vendor', 'gems', '*-*')].map do |gem_dir|
- Rails::GemDependency.from_directory_name(gem_dir, load_specs)
- end
-end
-
-def print_gem_status(gem, indent=1)
- code = case
- when gem.framework_gem? then 'R'
- when gem.frozen? then 'F'
- when gem.installed? then 'I'
- else ' '
- end
- puts " "*(indent-1)+" - [#{code}] #{gem.name} #{gem.requirement.to_s}"
- gem.dependencies.each { |g| print_gem_status(g, indent+1) }
-end
diff --git a/railties/lib/rails/vendor_gem_source_index.rb b/railties/lib/rails/vendor_gem_source_index.rb
deleted file mode 100644
index 5b7721f303..0000000000
--- a/railties/lib/rails/vendor_gem_source_index.rb
+++ /dev/null
@@ -1,140 +0,0 @@
-require 'rubygems'
-require 'yaml'
-
-module Rails
-
- class VendorGemSourceIndex
- # VendorGemSourceIndex acts as a proxy for the Gem source index, allowing
- # gems to be loaded from vendor/gems. Rather than the standard gem repository format,
- # vendor/gems contains unpacked gems, with YAML specifications in .specification in
- # each gem directory.
- include Enumerable
-
- attr_reader :installed_source_index
- attr_reader :vendor_source_index
-
- @@silence_spec_warnings = false
-
- def self.silence_spec_warnings
- @@silence_spec_warnings
- end
-
- def self.silence_spec_warnings=(v)
- @@silence_spec_warnings = v
- end
-
- def initialize(installed_index, vendor_dir=Rails::GemDependency.unpacked_path)
- @installed_source_index = installed_index
- @vendor_dir = vendor_dir
- refresh!
- end
-
- def refresh!
- # reload the installed gems
- @installed_source_index.refresh!
- vendor_gems = {}
-
- # handle vendor Rails gems - they are identified by having loaded_from set to ""
- # we add them manually to the list, so that other gems can find them via dependencies
- Gem.loaded_specs.each do |n, s|
- next unless s.loaded_from.empty?
- vendor_gems[s.full_name] = s
- end
-
- # load specifications from vendor/gems
- Dir[File.join(Rails::GemDependency.unpacked_path, '*')].each do |d|
- dir_name = File.basename(d)
- dir_version = version_for_dir(dir_name)
- spec = load_specification(d)
- if spec
- if spec.full_name != dir_name
- # mismatched directory name and gem spec - produced by 2.1.0-era unpack code
- if dir_version
- # fix the spec version - this is not optimal (spec.files may be wrong)
- # but it's better than breaking apps. Complain to remind users to get correct specs.
- # use ActiveSupport::Deprecation.warn, as the logger is not set yet
- $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems has a mismatched specification file."+
- " Run 'rake gems:refresh_specs' to fix this.") unless @@silence_spec_warnings
- spec.version = dir_version
- else
- $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems is not in a versioned directory"+
- "(should be #{spec.full_name}).") unless @@silence_spec_warnings
- # continue, assume everything is OK
- end
- end
- else
- # no spec - produced by early-2008 unpack code
- # emulate old behavior, and complain.
- $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems has no specification file."+
- " Run 'rake gems:refresh_specs' to fix this.") unless @@silence_spec_warnings
- if dir_version
- spec = Gem::Specification.new
- spec.version = dir_version
- spec.require_paths = ['lib']
- ext_path = File.join(d, 'ext')
- spec.require_paths << 'ext' if File.exist?(ext_path)
- spec.name = /^(.*)-[^-]+$/.match(dir_name)[1]
- files = ['lib']
- # set files to everything in lib/
- files += Dir[File.join(d, 'lib', '*')].map { |v| v.gsub(/^#{d}\//, '') }
- files += Dir[File.join(d, 'ext', '*')].map { |v| v.gsub(/^#{d}\//, '') } if ext_path
- spec.files = files
- else
- $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems not in a versioned directory."+
- " Giving up.") unless @@silence_spec_warnings
- next
- end
- end
- spec.loaded_from = File.join(d, '.specification')
- # finally, swap out full_gem_path
- # it would be better to use a Gem::Specification subclass, but the YAML loads an explicit class
- class << spec
- def full_gem_path
- path = File.join installation_path, full_name
- return path if File.directory? path
- File.join installation_path, original_name
- end
- end
- vendor_gems[File.basename(d)] = spec
- end
- @vendor_source_index = Gem::SourceIndex.new(vendor_gems)
- end
-
- def version_for_dir(d)
- matches = /-([^-]+)$/.match(d)
- Gem::Version.new(matches[1]) if matches
- end
-
- def load_specification(gem_dir)
- spec_file = File.join(gem_dir, '.specification')
- YAML.load_file(spec_file) if File.exist?(spec_file)
- end
-
- def find_name(*args)
- @installed_source_index.find_name(*args) + @vendor_source_index.find_name(*args)
- end
-
- def search(*args)
- # look for vendor gems, and then installed gems - later elements take priority
- @installed_source_index.search(*args) + @vendor_source_index.search(*args)
- end
-
- def each(&block)
- @vendor_source_index.each(&block)
- @installed_source_index.each(&block)
- end
-
- def add_spec(spec)
- @vendor_source_index.add_spec spec
- end
-
- def remove_spec(spec)
- @vendor_source_index.remove_spec spec
- end
-
- def size
- @vendor_source_index.size + @installed_source_index.size
- end
-
- end
-end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 0452208cae..a3e1916494 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -7,7 +7,6 @@ module ApplicationTests
def setup
build_app
boot_rails
- Object.send(:remove_const, :RAILS_ROOT)
end
test "the application root is set correctly" do
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index 03fecffdd0..445a867c85 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -7,6 +7,7 @@ module ApplicationTests
def setup
build_app
boot_rails
+ require "rails"
require "rails/generators"
end
diff --git a/railties/test/application/initializer_test.rb b/railties/test/application/initializer_test.rb
index c2e64374d0..f42954079b 100644
--- a/railties/test/application/initializer_test.rb
+++ b/railties/test/application/initializer_test.rb
@@ -7,6 +7,7 @@ module ApplicationTests
def setup
build_app
boot_rails
+ require "rails"
end
test "initializing an application initializes rails" do
diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb
index 0fdb4a083a..83c18be057 100644
--- a/railties/test/application/notifications_test.rb
+++ b/railties/test/application/notifications_test.rb
@@ -24,7 +24,7 @@ module ApplicationTests
def setup
build_app
boot_rails
-
+ require "rails"
require "active_support/notifications"
Rails::Initializer.run do |c|
c.notifications.queue = MyQueue.new
diff --git a/railties/test/application/plugins_test.rb b/railties/test/application/plugins_test.rb
index 0926ed106b..80a19856d7 100644
--- a/railties/test/application/plugins_test.rb
+++ b/railties/test/application/plugins_test.rb
@@ -11,6 +11,7 @@ module ApplicationTests
def setup
build_app
boot_rails
+ require "rails"
@failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
# Tmp hax to get tests working
FileUtils.cp_r "#{File.dirname(__FILE__)}/../fixtures/plugins", "#{app_path}/vendor"
diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb
index f9b9d3168d..0319d5f38c 100644
--- a/railties/test/backtrace_cleaner_test.rb
+++ b/railties/test/backtrace_cleaner_test.rb
@@ -50,10 +50,4 @@ class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase
end
end
- test "should format vendor gems correctly" do
- @backtrace = [ "#{Rails::GemDependency.unpacked_path}/nosuchgem-1.2.3/lib/foo.rb" ]
- @result = @cleaner.clean(@backtrace)
- assert_equal "nosuchgem (1.2.3) [v] lib/foo.rb", @result[0]
- end
-
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 7e6b7b183c..2e19169d3d 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -101,10 +101,11 @@ class GeneratorsTest < GeneratorsTestCase
def test_rails_generators_with_others_information
output = capture(:stdout){ Rails::Generators.help }.split("\n").last
- assert_equal "Others: active_record:fixjour, fixjour, mspec, rails:javascripts, wrong.", output
+ assert_equal "Others: active_record:fixjour, fixjour, mspec, rails:javascripts.", output
end
def test_warning_is_shown_if_generator_cant_be_loaded
+ Rails::Generators.load_paths << File.expand_path("../fixtures/vendor/gems/gems/wrong", __FILE__)
output = capture(:stderr){ Rails::Generators.find_by_namespace(:wrong) }
assert_match /\[WARNING\] Could not load generator at/, output
assert_match /Error: uninitialized constant Rails::Generator/, output
diff --git a/railties/test/initializer/check_ruby_version_test.rb b/railties/test/initializer/check_ruby_version_test.rb
index 0c725311ad..cf956e68fb 100644
--- a/railties/test/initializer/check_ruby_version_test.rb
+++ b/railties/test/initializer/check_ruby_version_test.rb
@@ -7,6 +7,7 @@ module InitializerTests
def setup
build_app
boot_rails
+ require "rails"
end
test "rails does not initialize with ruby version 1.8.1" do
diff --git a/railties/test/initializer/initialize_i18n_test.rb b/railties/test/initializer/initialize_i18n_test.rb
index 04b44cedd0..d664f96ad1 100644
--- a/railties/test/initializer/initialize_i18n_test.rb
+++ b/railties/test/initializer/initialize_i18n_test.rb
@@ -7,6 +7,7 @@ module InitializerTests
def setup
build_app
boot_rails
+ require "rails"
end
# test_config_defaults_and_settings_should_be_added_to_i18n_defaults
diff --git a/railties/test/initializer/path_test.rb b/railties/test/initializer/path_test.rb
index 9c36bb2000..1b58a58555 100644
--- a/railties/test/initializer/path_test.rb
+++ b/railties/test/initializer/path_test.rb
@@ -6,6 +6,7 @@ class PathsTest < Test::Unit::TestCase
def setup
build_app
boot_rails
+ require "rails"
Rails::Initializer.run do |config|
config.root = app_path
config.frameworks = [:action_controller, :action_view, :action_mailer, :active_record]
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 750ec5d319..aafc9f68bb 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -114,16 +114,19 @@ module TestHelpers
end
def boot_rails
- # TMP mega hax to prevent boot.rb from actually booting
- Object.class_eval <<-RUBY, __FILE__, __LINE__+1
- module Rails
- Initializer = 'lol'
- require "#{app_path}/config/boot"
- remove_const(:Initializer)
- booter = VendorBoot.new('#{app_path}')
- booter.run
- end
- RUBY
+ %w(
+ actionmailer/lib
+ actionpack/lib
+ activemodel/lib
+ activerecord/lib
+ activeresource/lib
+ activesupport/lib
+ railties/lib
+ railties
+ ).reverse_each do |path|
+ path = File.expand_path("../../../../#{path}", __FILE__)
+ $:.unshift(path)
+ end
end
end
end
--
cgit v1.2.3
From e13d232150921cdf0ec3d713caefa628d235152e Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 16 Oct 2009 20:57:46 -0500
Subject: Add stub for configuring your package manager
---
railties/lib/rails/generators/rails/app/app_generator.rb | 6 +++++-
.../generators/rails/app/templates/config/application.rb | 12 ++----------
.../rails/generators/rails/app/templates/config/boot.rb | 15 +++++++++++++++
.../generators/rails/app/templates/config/environment.rb | 6 +-----
4 files changed, 23 insertions(+), 16 deletions(-)
create mode 100644 railties/lib/rails/generators/rails/app/templates/config/boot.rb
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 20843cd720..93d9ac553d 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -1,4 +1,4 @@
-require 'digest/md5'
+require 'digest/md5'
require 'active_support/secure_random'
require 'rails/version' unless defined?(Rails::VERSION)
@@ -75,6 +75,10 @@ module Rails::Generators
end
end
+ def create_boot_file
+ copy_file "config/boot.rb"
+ end
+
def create_activerecord_files
return if options[:skip_activerecord]
template "config/databases/#{options[:database]}.yml", "config/database.yml"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 451dba86fb..7ba7c52f9e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -1,12 +1,4 @@
-begin
- # Use Bundler
- require File.expand_path("../../vendor/gems/environment", __FILE__)
-rescue LoadError
- # Use Rubygems
- require 'rubygems'
-end
-
-require 'rails'
+require File.expand_path(File.join(File.dirname(__FILE__), 'boot'))
Rails::Initializer.run do |config|
# Settings in config/environments/* take precedence over those specified here.
@@ -52,4 +44,4 @@ Rails::Initializer.run do |config|
# g.template_engine :erb
# g.test_framework :test_unit, :fixture => true
# end
-end
\ No newline at end of file
+end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
new file mode 100644
index 0000000000..3165a4091e
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
@@ -0,0 +1,15 @@
+# Package management
+# Choose one
+
+# Use Bundler (preferred)
+environment = File.expand_path('../../vendor/gems/environment', __FILE__)
+require environment if File.exist?(environment)
+
+# Use 2.x style vendor/rails directory
+vendor_rails = File.expand_path('../../vendor/rails', __FILE__)
+Dir["#{vendor_rails}/*/lib"].each { |path| $:.unshift(path) } if File.exist?(vendor_rails)
+
+# Load Rails from traditional RubyGems
+require 'rubygems'
+
+require 'rails'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
index 3bb0f2619e..e5362b21cc 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
@@ -1,9 +1,5 @@
-# Be sure to restart your server when you modify this file
-
-# Specifies gem version of Rails to use when vendor/rails is not present
-<%= '# ' if options[:freeze] %>RAILS_GEM_VERSION = '<%= Rails::VERSION::STRING %>' unless defined? RAILS_GEM_VERSION
-
# Load the rails application
require File.expand_path(File.join(File.dirname(__FILE__), 'application'))
+
# Initialize the rails application
Rails.initialize!
--
cgit v1.2.3
From 2e37effd7203cad84459661e11db2be44586cb4f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Sat, 17 Oct 2009 12:54:03 -0300
Subject: Unify class_inheritable_accessor and extlib_inheritable_accessor and
allow responder to be set in the class level.
---
.../lib/action_controller/metal/mime_responds.rb | 9 +++----
actionpack/test/controller/mime_responds_test.rb | 8 +++++++
.../core_ext/class/inheritable_attributes.rb | 28 ++++++++++++----------
.../active_support/core_ext/object/duplicable.rb | 8 ++++++-
4 files changed, 34 insertions(+), 19 deletions(-)
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 3026067868..468c5f4fae 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -3,7 +3,8 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
included do
- class_inheritable_reader :mimes_for_respond_to
+ extlib_inheritable_accessor :responder, :mimes_for_respond_to, :instance_writer => false
+ self.responder = ActionController::Responder
clear_respond_to
end
@@ -46,7 +47,7 @@ module ActionController #:nodoc:
# Clear all mimes in respond_to.
#
def clear_respond_to
- write_inheritable_attribute(:mimes_for_respond_to, ActiveSupport::OrderedHash.new)
+ self.mimes_for_respond_to = ActiveSupport::OrderedHash.new
end
end
@@ -221,10 +222,6 @@ module ActionController #:nodoc:
end
end
- def responder
- ActionController::Responder
- end
-
protected
# Collect mimes declared in the class method respond_to valid for the
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index a79648396c..b070f925d4 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -760,6 +760,14 @@ class RespondWithControllerTest < ActionController::TestCase
assert_equal "Resource name is david", @response.body
end
+ def test_using_resource_with_responder
+ RespondWithController.responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" }
+ get :using_resource
+ assert_equal "Resource name is david", @response.body
+ ensure
+ RespondWithController.responder = ActionController::Responder
+ end
+
def test_not_acceptable
@request.accept = "application/xml"
get :using_defaults
diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
index 8bac2dff19..d8e9768a5e 100644
--- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
@@ -10,16 +10,19 @@ end
# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
class Class # :nodoc:
def class_inheritable_reader(*syms)
+ options = syms.extract_options!
syms.each do |sym|
next if sym.is_a?(Hash)
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def self.#{sym} # def self.after_add
- read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:after_add)
- end # end
-
- def #{sym} # def after_add
- self.class.#{sym} # self.class.after_add
- end # end
+ def self.#{sym} # def self.after_add
+ read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:after_add)
+ end # end
+ #
+ #{" #
+ def #{sym} # def after_add
+ self.class.#{sym} # self.class.after_add
+ end # end
+ " unless options[:instance_reader] == false } # # the reader above is generated unless options[:instance_reader] == false
EOS
end
end
@@ -156,7 +159,7 @@ class Class
# moving on). In particular, this makes the return value of this function
# less useful.
def extlib_inheritable_reader(*ivars)
- instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash)
+ options = ivars.extract_options!
ivars.each do |ivar|
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -164,10 +167,10 @@ class Class
return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar})
ivar = superclass.#{ivar}
return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}")
- @#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) ? ivar.dup : ivar
+ @#{ivar} = ivar.duplicable? ? ivar.dup : ivar
end
RUBY
- unless instance_reader == false
+ unless options[:instance_reader] == false
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{ivar}
self.class.#{ivar}
@@ -190,14 +193,15 @@ class Class
# @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
# class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
def extlib_inheritable_writer(*ivars)
- instance_writer = ivars.pop[:writer] if ivars.last.is_a?(Hash)
+ options = ivars.extract_options!
+
ivars.each do |ivar|
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def self.#{ivar}=(obj)
@#{ivar} = obj
end
RUBY
- unless instance_writer == false
+ unless options[:instance_writer] == false
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{ivar}=(obj) self.class.#{ivar} = obj end
RUBY
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 1722726ca2..a2d4d50076 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -1,6 +1,6 @@
class Object
# Can you safely .dup this object?
- # False for nil, false, true, symbols, numbers, and class objects; true otherwise.
+ # False for nil, false, true, symbols, numbers, class and module objects; true otherwise.
def duplicable?
true
end
@@ -41,3 +41,9 @@ class Class #:nodoc:
false
end
end
+
+class Module #:nodoc:
+ def duplicable?
+ false
+ end
+end
--
cgit v1.2.3
From cb873026898badc5c3dc61a95a08051cf218251f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Sat, 17 Oct 2009 13:03:28 -0300
Subject: Bring agnosticism to error_messages_for.
---
.../lib/action_view/helpers/active_model_helper.rb | 16 +-
actionpack/lib/action_view/locale/en.yml | 4 +-
.../test/template/active_model_helper_i18n_test.rb | 42 +++
.../test/template/active_model_helper_test.rb | 314 +++++++++++++++++++++
.../template/active_record_helper_i18n_test.rb | 51 ----
.../test/template/active_record_helper_test.rb | 314 ---------------------
6 files changed, 368 insertions(+), 373 deletions(-)
create mode 100644 actionpack/test/template/active_model_helper_i18n_test.rb
create mode 100644 actionpack/test/template/active_model_helper_test.rb
delete mode 100644 actionpack/test/template/active_record_helper_i18n_test.rb
delete mode 100644 actionpack/test/template/active_record_helper_test.rb
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 7cc1e48572..3c398fe4da 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -202,8 +202,8 @@ module ActionView
end
objects.compact!
-
count = objects.inject(0) {|sum, object| sum + object.errors.count }
+
unless count.zero?
html = {}
[:id, :class].each do |key|
@@ -216,16 +216,20 @@ module ActionView
end
options[:object_name] ||= params.first
- I18n.with_options :locale => options[:locale], :scope => [:activerecord, :errors, :template] do |locale|
+ I18n.with_options :locale => options[:locale], :scope => [:activemodel, :errors, :template] do |locale|
header_message = if options.include?(:header_message)
options[:header_message]
else
- object_name = options[:object_name].to_s.gsub('_', ' ')
- object_name = I18n.t(options[:object_name].to_s, :default => object_name, :scope => [:activerecord, :models], :count => 1)
- locale.t :header, :count => count, :model => object_name
+ locale.t :header, :count => count, :model => options[:object_name].to_s.gsub('_', ' ')
end
+
message = options.include?(:message) ? options[:message] : locale.t(:body)
- error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, ERB::Util.html_escape(msg)) } }.join
+
+ error_messages = objects.sum do |object|
+ object.errors.full_messages.map do |msg|
+ content_tag(:li, ERB::Util.html_escape(msg))
+ end
+ end.join
contents = ''
contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index 84d94fd700..5e2a92b89a 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -102,7 +102,7 @@
minute: "Minute"
second: "Seconds"
- activerecord:
+ activemodel:
errors:
template:
header:
@@ -114,4 +114,4 @@
support:
select:
# default value for :prompt => true in FormOptionsHelper
- prompt: "Please select"
\ No newline at end of file
+ prompt: "Please select"
diff --git a/actionpack/test/template/active_model_helper_i18n_test.rb b/actionpack/test/template/active_model_helper_i18n_test.rb
new file mode 100644
index 0000000000..2465444fc5
--- /dev/null
+++ b/actionpack/test/template/active_model_helper_i18n_test.rb
@@ -0,0 +1,42 @@
+require 'abstract_unit'
+
+class ActiveModelHelperI18nTest < Test::Unit::TestCase
+ include ActionView::Context
+ include ActionView::Helpers::ActiveModelHelper
+
+ attr_reader :request
+
+ def setup
+ @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
+ @object.stubs :to_model => @object
+ @object.stubs :class => stub(:model_name => stub(:human => ""))
+
+ @object_name = 'book_seller'
+ @object_name_without_underscore = 'book seller'
+
+ stubs(:content_tag).returns 'content_tag'
+
+ I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
+ I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:'
+ end
+
+ def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message
+ I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').never
+ error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en')
+ end
+
+ def test_error_messages_for_given_no_header_option_it_translates_header_message
+ I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns 'header message'
+ error_messages_for(:object => @object, :locale => 'en')
+ end
+
+ def test_error_messages_for_given_a_message_option_it_does_not_translate_message
+ I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).never
+ error_messages_for(:object => @object, :message => 'message', :locale => 'en')
+ end
+
+ def test_error_messages_for_given_no_message_option_it_translates_message
+ I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:'
+ error_messages_for(:object => @object, :locale => 'en')
+ end
+end
diff --git a/actionpack/test/template/active_model_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb
new file mode 100644
index 0000000000..3e01ae78c3
--- /dev/null
+++ b/actionpack/test/template/active_model_helper_test.rb
@@ -0,0 +1,314 @@
+require 'abstract_unit'
+
+class ActiveModelHelperTest < ActionView::TestCase
+ tests ActionView::Helpers::ActiveModelHelper
+
+ silence_warnings do
+ class Post < Struct.new(:title, :author_name, :body, :secret, :written_on)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ end
+
+ class User < Struct.new(:email)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ end
+
+ class Column < Struct.new(:type, :name, :human_name)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ end
+ end
+
+ class DirtyPost
+ class Errors
+ def empty?
+ false
+ end
+
+ def count
+ 1
+ end
+
+ def full_messages
+ ["Author name can't be empty"]
+ end
+
+ def [](field)
+ ["can't be empty"]
+ end
+ end
+
+ def errors
+ Errors.new
+ end
+ end
+
+ def setup_post
+ @post = Post.new
+ def @post.errors
+ Class.new {
+ def [](field)
+ case field.to_s
+ when "author_name"
+ ["can't be empty"]
+ when "body"
+ ['foo']
+ else
+ []
+ end
+ end
+ def empty?() false end
+ def count() 1 end
+ def full_messages() [ "Author name can't be empty" ] end
+ }.new
+ end
+
+ def @post.new_record?() true end
+ def @post.to_param() nil end
+
+ def @post.column_for_attribute(attr_name)
+ Post.content_columns.select { |column| column.name == attr_name }.first
+ end
+
+ silence_warnings do
+ def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end
+ end
+
+ @post.title = "Hello World"
+ @post.author_name = ""
+ @post.body = "Back to the hill and over it again!"
+ @post.secret = 1
+ @post.written_on = Date.new(2004, 6, 15)
+ end
+
+ def setup_user
+ @user = User.new
+ def @user.errors
+ Class.new {
+ def [](field) field == "email" ? ['nonempty'] : [] end
+ def empty?() false end
+ def count() 1 end
+ def full_messages() [ "User email can't be empty" ] end
+ }.new
+ end
+
+ def @user.new_record?() true end
+ def @user.to_param() nil end
+
+ def @user.column_for_attribute(attr_name)
+ User.content_columns.select { |column| column.name == attr_name }.first
+ end
+
+ silence_warnings do
+ def User.content_columns() [ Column.new(:string, "email", "Email") ] end
+ end
+
+ @user.email = ""
+ end
+
+ def protect_against_forgery?
+ @protect_against_forgery ? true : false
+ end
+ attr_accessor :request_forgery_protection_token, :form_authenticity_token
+
+ def setup
+ super
+ setup_post
+ setup_user
+
+ @response = ActionController::TestResponse.new
+
+ @controller = Object.new
+ def @controller.url_for(options)
+ options = options.symbolize_keys
+
+ [options[:action], options[:id].to_param].compact.join('/')
+ end
+ end
+
+ def test_generic_input_tag
+ assert_dom_equal(
+ %(), input("post", "title")
+ )
+ end
+
+ def test_text_area_with_errors
+ assert_dom_equal(
+ %(),
+ text_area("post", "body")
+ )
+ end
+
+ def test_text_field_with_errors
+ assert_dom_equal(
+ %(),
+ text_field("post", "author_name")
+ )
+ end
+
+ def test_form_with_string
+ assert_dom_equal(
+ %(
),
+ form("post")
+ )
+
+ silence_warnings do
+ class << @post
+ def new_record?() false end
+ def to_param() id end
+ def id() 1 end
+ end
+ end
+
+ assert_dom_equal(
+ %(),
+ form("post")
+ )
+ end
+
+ def test_form_with_protect_against_forgery
+ @protect_against_forgery = true
+ @request_forgery_protection_token = 'authenticity_token'
+ @form_authenticity_token = '123'
+ assert_dom_equal(
+ %(),
+ form("post")
+ )
+ end
+
+ def test_form_with_method_option
+ assert_dom_equal(
+ %(),
+ form("post", :method=>'get')
+ )
+ end
+
+ def test_form_with_action_option
+ output_buffer << form("post", :action => "sign")
+ assert_select "form[action=sign]" do |form|
+ assert_select "input[type=submit][value=Sign]"
+ end
+ end
+
+ def test_form_with_date
+ silence_warnings do
+ def Post.content_columns() [ Column.new(:date, "written_on", "Written on") ] end
+ end
+
+ assert_dom_equal(
+ %(),
+ form("post")
+ )
+ end
+
+ def test_form_with_datetime
+ silence_warnings do
+ def Post.content_columns() [ Column.new(:datetime, "written_on", "Written on") ] end
+ end
+ @post.written_on = Time.gm(2004, 6, 15, 16, 30)
+
+ assert_dom_equal(
+ %(),
+ form("post")
+ )
+ end
+
+ def test_error_for_block
+ assert_dom_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
), error_messages_for("post")
+ assert_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
), error_messages_for("post", :class => "errorDeathByClass", :id => "errorDeathById", :header_tag => "h1")
+ assert_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
), error_messages_for("post", :class => nil, :id => "errorDeathById", :header_tag => "h1")
+ assert_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
), error_messages_for("post", :class => "errorDeathByClass", :id => nil, :header_tag => "h1")
+ end
+
+ def test_error_messages_for_escapes_html
+ @dirty_post = DirtyPost.new
+ assert_dom_equal %(1 error prohibited this dirty post from being saved
There were problems with the following fields:
- Author name can't be <em>empty</em>
), error_messages_for("dirty_post")
+ end
+
+ def test_error_messages_for_handles_nil
+ assert_equal "", error_messages_for("notthere")
+ end
+
+ def test_error_message_on_escapes_html
+ @dirty_post = DirtyPost.new
+ assert_dom_equal "can't be <em>empty</em>
", error_message_on(:dirty_post, :author_name)
+ end
+
+ def test_error_message_on_handles_nil
+ assert_equal "", error_message_on("notthere", "notthere")
+ end
+
+ def test_error_message_on
+ assert_dom_equal "can't be empty
", error_message_on(:post, :author_name)
+ end
+
+ def test_error_message_on_no_instance_variable
+ other_post = @post
+ assert_dom_equal "can't be empty
", error_message_on(other_post, :author_name)
+ end
+
+ def test_error_message_on_with_options_hash
+ assert_dom_equal "beforecan't be emptyafter
", error_message_on(:post, :author_name, :css_class => 'differentError', :prepend_text => 'before', :append_text => 'after')
+ end
+
+ def test_error_messages_for_many_objects
+ assert_dom_equal %(2 errors prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
- User email can't be empty
), error_messages_for("post", "user")
+
+ # reverse the order, error order changes and so does the title
+ assert_dom_equal %(2 errors prohibited this user from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for("user", "post")
+
+ # add the default to put post back in the title
+ assert_dom_equal %(2 errors prohibited this post from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for("user", "post", :object_name => "post")
+
+ # symbols work as well
+ assert_dom_equal %(2 errors prohibited this post from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for(:user, :post, :object_name => :post)
+
+ # any default works too
+ assert_dom_equal %(2 errors prohibited this monkey from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for(:user, :post, :object_name => "monkey")
+
+ # should space object name
+ assert_dom_equal %(2 errors prohibited this chunky bacon from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for(:user, :post, :object_name => "chunky_bacon")
+
+ # hide header and explanation messages with nil or empty string
+ assert_dom_equal %(- User email can't be empty
- Author name can't be empty
), error_messages_for(:user, :post, :header_message => nil, :message => "")
+
+ # override header and explanation messages
+ header_message = "Yikes! Some errors"
+ message = "Please fix the following fields and resubmit:"
+ assert_dom_equal %(#{header_message}
#{message}
- User email can't be empty
- Author name can't be empty
), error_messages_for(:user, :post, :header_message => header_message, :message => message)
+ end
+
+ def test_error_messages_for_non_instance_variable
+ actual_user = @user
+ actual_post = @post
+ @user = nil
+ @post = nil
+
+ #explicitly set object
+ assert_dom_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
), error_messages_for("post", :object => actual_post)
+
+ #multiple objects
+ assert_dom_equal %(2 errors prohibited this user from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for("user", "post", :object => [actual_user, actual_post])
+
+ #nil object
+ assert_equal '', error_messages_for('user', :object => nil)
+ end
+
+ def test_error_messages_for_model_objects
+ error = error_messages_for(@post)
+ assert_dom_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
),
+ error
+
+ error = error_messages_for(@user, @post)
+ assert_dom_equal %(2 errors prohibited this user from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
),
+ error
+ end
+
+ def test_form_with_string_multipart
+ assert_dom_equal(
+ %(),
+ form("post", :multipart => true)
+ )
+ end
+end
diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb
deleted file mode 100644
index 047f81be29..0000000000
--- a/actionpack/test/template/active_record_helper_i18n_test.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'abstract_unit'
-
-class ActiveRecordHelperI18nTest < Test::Unit::TestCase
- include ActionView::Context
- include ActionView::Helpers::ActiveModelHelper
-
- attr_reader :request
-
- def setup
- @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
- @object.stubs :to_model => @object
- @object.stubs :class => stub(:model_name => stub(:human => ""))
-
- @object_name = 'book_seller'
- @object_name_without_underscore = 'book seller'
-
- stubs(:content_tag).returns 'content_tag'
-
- I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
- I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:'
- end
-
- def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message
- I18n.expects(:translate).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').never
- error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en')
- end
-
- def test_error_messages_for_given_no_header_option_it_translates_header_message
- I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns 'header message'
- I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns ''
- error_messages_for(:object => @object, :locale => 'en')
- end
-
- def test_error_messages_for_given_a_message_option_it_does_not_translate_message
- I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).never
- I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns ''
- error_messages_for(:object => @object, :message => 'message', :locale => 'en')
- end
-
- def test_error_messages_for_given_no_message_option_it_translates_message
- I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:'
- I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns ''
- error_messages_for(:object => @object, :locale => 'en')
- end
-
- def test_error_messages_for_given_object_name_it_translates_object_name
- I18n.expects(:t).with(:header, :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => @object_name_without_underscore).returns "1 error prohibited this #{@object_name_without_underscore} from being saved"
- I18n.expects(:t).with(@object_name, :default => @object_name_without_underscore, :count => 1, :scope => [:activerecord, :models]).once.returns @object_name_without_underscore
- error_messages_for(:object => @object, :locale => 'en', :object_name => @object_name)
- end
-end
diff --git a/actionpack/test/template/active_record_helper_test.rb b/actionpack/test/template/active_record_helper_test.rb
deleted file mode 100644
index c149070f2a..0000000000
--- a/actionpack/test/template/active_record_helper_test.rb
+++ /dev/null
@@ -1,314 +0,0 @@
-require 'abstract_unit'
-
-class ActiveRecordHelperTest < ActionView::TestCase
- tests ActionView::Helpers::ActiveModelHelper
-
- silence_warnings do
- class Post < Struct.new(:title, :author_name, :body, :secret, :written_on)
- extend ActiveModel::Naming
- include ActiveModel::Conversion
- end
-
- class User < Struct.new(:email)
- extend ActiveModel::Naming
- include ActiveModel::Conversion
- end
-
- class Column < Struct.new(:type, :name, :human_name)
- extend ActiveModel::Naming
- include ActiveModel::Conversion
- end
- end
-
- class DirtyPost
- class Errors
- def empty?
- false
- end
-
- def count
- 1
- end
-
- def full_messages
- ["Author name can't be empty"]
- end
-
- def [](field)
- ["can't be empty"]
- end
- end
-
- def errors
- Errors.new
- end
- end
-
- def setup_post
- @post = Post.new
- def @post.errors
- Class.new {
- def [](field)
- case field.to_s
- when "author_name"
- ["can't be empty"]
- when "body"
- ['foo']
- else
- []
- end
- end
- def empty?() false end
- def count() 1 end
- def full_messages() [ "Author name can't be empty" ] end
- }.new
- end
-
- def @post.new_record?() true end
- def @post.to_param() nil end
-
- def @post.column_for_attribute(attr_name)
- Post.content_columns.select { |column| column.name == attr_name }.first
- end
-
- silence_warnings do
- def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end
- end
-
- @post.title = "Hello World"
- @post.author_name = ""
- @post.body = "Back to the hill and over it again!"
- @post.secret = 1
- @post.written_on = Date.new(2004, 6, 15)
- end
-
- def setup_user
- @user = User.new
- def @user.errors
- Class.new {
- def [](field) field == "email" ? ['nonempty'] : [] end
- def empty?() false end
- def count() 1 end
- def full_messages() [ "User email can't be empty" ] end
- }.new
- end
-
- def @user.new_record?() true end
- def @user.to_param() nil end
-
- def @user.column_for_attribute(attr_name)
- User.content_columns.select { |column| column.name == attr_name }.first
- end
-
- silence_warnings do
- def User.content_columns() [ Column.new(:string, "email", "Email") ] end
- end
-
- @user.email = ""
- end
-
- def protect_against_forgery?
- @protect_against_forgery ? true : false
- end
- attr_accessor :request_forgery_protection_token, :form_authenticity_token
-
- def setup
- super
- setup_post
- setup_user
-
- @response = ActionController::TestResponse.new
-
- @controller = Object.new
- def @controller.url_for(options)
- options = options.symbolize_keys
-
- [options[:action], options[:id].to_param].compact.join('/')
- end
- end
-
- def test_generic_input_tag
- assert_dom_equal(
- %(), input("post", "title")
- )
- end
-
- def test_text_area_with_errors
- assert_dom_equal(
- %(),
- text_area("post", "body")
- )
- end
-
- def test_text_field_with_errors
- assert_dom_equal(
- %(),
- text_field("post", "author_name")
- )
- end
-
- def test_form_with_string
- assert_dom_equal(
- %(),
- form("post")
- )
-
- silence_warnings do
- class << @post
- def new_record?() false end
- def to_param() id end
- def id() 1 end
- end
- end
-
- assert_dom_equal(
- %(),
- form("post")
- )
- end
-
- def test_form_with_protect_against_forgery
- @protect_against_forgery = true
- @request_forgery_protection_token = 'authenticity_token'
- @form_authenticity_token = '123'
- assert_dom_equal(
- %(),
- form("post")
- )
- end
-
- def test_form_with_method_option
- assert_dom_equal(
- %(),
- form("post", :method=>'get')
- )
- end
-
- def test_form_with_action_option
- output_buffer << form("post", :action => "sign")
- assert_select "form[action=sign]" do |form|
- assert_select "input[type=submit][value=Sign]"
- end
- end
-
- def test_form_with_date
- silence_warnings do
- def Post.content_columns() [ Column.new(:date, "written_on", "Written on") ] end
- end
-
- assert_dom_equal(
- %(),
- form("post")
- )
- end
-
- def test_form_with_datetime
- silence_warnings do
- def Post.content_columns() [ Column.new(:datetime, "written_on", "Written on") ] end
- end
- @post.written_on = Time.gm(2004, 6, 15, 16, 30)
-
- assert_dom_equal(
- %(),
- form("post")
- )
- end
-
- def test_error_for_block
- assert_dom_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
), error_messages_for("post")
- assert_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
), error_messages_for("post", :class => "errorDeathByClass", :id => "errorDeathById", :header_tag => "h1")
- assert_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
), error_messages_for("post", :class => nil, :id => "errorDeathById", :header_tag => "h1")
- assert_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
), error_messages_for("post", :class => "errorDeathByClass", :id => nil, :header_tag => "h1")
- end
-
- def test_error_messages_for_escapes_html
- @dirty_post = DirtyPost.new
- assert_dom_equal %(1 error prohibited this dirty post from being saved
There were problems with the following fields:
- Author name can't be <em>empty</em>
), error_messages_for("dirty_post")
- end
-
- def test_error_messages_for_handles_nil
- assert_equal "", error_messages_for("notthere")
- end
-
- def test_error_message_on_escapes_html
- @dirty_post = DirtyPost.new
- assert_dom_equal "can't be <em>empty</em>
", error_message_on(:dirty_post, :author_name)
- end
-
- def test_error_message_on_handles_nil
- assert_equal "", error_message_on("notthere", "notthere")
- end
-
- def test_error_message_on
- assert_dom_equal "can't be empty
", error_message_on(:post, :author_name)
- end
-
- def test_error_message_on_no_instance_variable
- other_post = @post
- assert_dom_equal "can't be empty
", error_message_on(other_post, :author_name)
- end
-
- def test_error_message_on_with_options_hash
- assert_dom_equal "beforecan't be emptyafter
", error_message_on(:post, :author_name, :css_class => 'differentError', :prepend_text => 'before', :append_text => 'after')
- end
-
- def test_error_messages_for_many_objects
- assert_dom_equal %(2 errors prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
- User email can't be empty
), error_messages_for("post", "user")
-
- # reverse the order, error order changes and so does the title
- assert_dom_equal %(2 errors prohibited this user from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for("user", "post")
-
- # add the default to put post back in the title
- assert_dom_equal %(2 errors prohibited this post from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for("user", "post", :object_name => "post")
-
- # symbols work as well
- assert_dom_equal %(2 errors prohibited this post from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for(:user, :post, :object_name => :post)
-
- # any default works too
- assert_dom_equal %(2 errors prohibited this monkey from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for(:user, :post, :object_name => "monkey")
-
- # should space object name
- assert_dom_equal %(2 errors prohibited this chunky bacon from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for(:user, :post, :object_name => "chunky_bacon")
-
- # hide header and explanation messages with nil or empty string
- assert_dom_equal %(- User email can't be empty
- Author name can't be empty
), error_messages_for(:user, :post, :header_message => nil, :message => "")
-
- # override header and explanation messages
- header_message = "Yikes! Some errors"
- message = "Please fix the following fields and resubmit:"
- assert_dom_equal %(#{header_message}
#{message}
- User email can't be empty
- Author name can't be empty
), error_messages_for(:user, :post, :header_message => header_message, :message => message)
- end
-
- def test_error_messages_for_non_instance_variable
- actual_user = @user
- actual_post = @post
- @user = nil
- @post = nil
-
- #explicitly set object
- assert_dom_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
), error_messages_for("post", :object => actual_post)
-
- #multiple objects
- assert_dom_equal %(2 errors prohibited this user from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
), error_messages_for("user", "post", :object => [actual_user, actual_post])
-
- #nil object
- assert_equal '', error_messages_for('user', :object => nil)
- end
-
- def test_error_messages_for_model_objects
- error = error_messages_for(@post)
- assert_dom_equal %(1 error prohibited this post from being saved
There were problems with the following fields:
- Author name can't be empty
),
- error
-
- error = error_messages_for(@user, @post)
- assert_dom_equal %(2 errors prohibited this user from being saved
There were problems with the following fields:
- User email can't be empty
- Author name can't be empty
),
- error
- end
-
- def test_form_with_string_multipart
- assert_dom_equal(
- %(),
- form("post", :multipart => true)
- )
- end
-end
--
cgit v1.2.3
From f936a1f100e75082081e782e5cceb272885c2df7 Mon Sep 17 00:00:00 2001
From: Eric Chapweske
Date: Sat, 17 Oct 2009 12:37:15 -0500
Subject: Refactoring attributes/types [#3348 state:resolved]
Signed-off-by: Joshua Peek
---
activerecord/lib/active_record.rb | 16 +++
.../attribute_methods/before_type_cast.rb | 13 +--
.../lib/active_record/attribute_methods/query.rb | 20 +---
.../lib/active_record/attribute_methods/read.rb | 49 +--------
.../attribute_methods/time_zone_conversion.rb | 48 ++-------
.../lib/active_record/attribute_methods/write.rb | 9 +-
activerecord/lib/active_record/attributes.rb | 37 +++++++
.../lib/active_record/attributes/aliasing.rb | 42 ++++++++
activerecord/lib/active_record/attributes/store.rb | 15 +++
.../lib/active_record/attributes/typecasting.rb | 111 +++++++++++++++++++
activerecord/lib/active_record/base.rb | 38 ++-----
activerecord/lib/active_record/types.rb | 38 +++++++
activerecord/lib/active_record/types/number.rb | 30 ++++++
activerecord/lib/active_record/types/object.rb | 37 +++++++
activerecord/lib/active_record/types/serialize.rb | 33 ++++++
.../lib/active_record/types/time_with_zone.rb | 20 ++++
activerecord/lib/active_record/types/unknown.rb | 37 +++++++
.../test/cases/attributes/aliasing_test.rb | 20 ++++
.../test/cases/attributes/typecasting_test.rb | 118 +++++++++++++++++++++
activerecord/test/cases/types/number_test.rb | 30 ++++++
activerecord/test/cases/types/object_test.rb | 24 +++++
activerecord/test/cases/types/serialize_test.rb | 20 ++++
.../test/cases/types/time_with_zone_test.rb | 42 ++++++++
activerecord/test/cases/types/unknown_test.rb | 29 +++++
activerecord/test/cases/types_test.rb | 32 ++++++
25 files changed, 760 insertions(+), 148 deletions(-)
create mode 100644 activerecord/lib/active_record/attributes.rb
create mode 100644 activerecord/lib/active_record/attributes/aliasing.rb
create mode 100644 activerecord/lib/active_record/attributes/store.rb
create mode 100644 activerecord/lib/active_record/attributes/typecasting.rb
create mode 100644 activerecord/lib/active_record/types.rb
create mode 100644 activerecord/lib/active_record/types/number.rb
create mode 100644 activerecord/lib/active_record/types/object.rb
create mode 100644 activerecord/lib/active_record/types/serialize.rb
create mode 100644 activerecord/lib/active_record/types/time_with_zone.rb
create mode 100644 activerecord/lib/active_record/types/unknown.rb
create mode 100644 activerecord/test/cases/attributes/aliasing_test.rb
create mode 100644 activerecord/test/cases/attributes/typecasting_test.rb
create mode 100644 activerecord/test/cases/types/number_test.rb
create mode 100644 activerecord/test/cases/types/object_test.rb
create mode 100644 activerecord/test/cases/types/serialize_test.rb
create mode 100644 activerecord/test/cases/types/time_with_zone_test.rb
create mode 100644 activerecord/test/cases/types/unknown_test.rb
create mode 100644 activerecord/test/cases/types_test.rb
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 2d66fa9fcb..8f118a6057 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -51,6 +51,7 @@ module ActiveRecord
autoload :AssociationPreload, 'active_record/association_preload'
autoload :Associations, 'active_record/associations'
autoload :AttributeMethods, 'active_record/attribute_methods'
+ autoload :Attributes, 'active_record/attributes'
autoload :AutosaveAssociation, 'active_record/autosave_association'
autoload :Relation, 'active_record/relation'
autoload :Base, 'active_record/base'
@@ -74,6 +75,7 @@ module ActiveRecord
autoload :TestCase, 'active_record/test_case'
autoload :Timestamp, 'active_record/timestamp'
autoload :Transactions, 'active_record/transactions'
+ autoload :Types, 'active_record/types'
autoload :Validator, 'active_record/validator'
autoload :Validations, 'active_record/validations'
@@ -87,6 +89,20 @@ module ActiveRecord
autoload :Write, 'active_record/attribute_methods/write'
end
+ module Attributes
+ autoload :Aliasing, 'active_record/attributes/aliasing'
+ autoload :Store, 'active_record/attributes/store'
+ autoload :Typecasting, 'active_record/attributes/typecasting'
+ end
+
+ module Type
+ 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
+
module Locking
autoload :Optimistic, 'active_record/locking/optimistic'
autoload :Pessimistic, 'active_record/locking/pessimistic'
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index a4e144f233..74921241f7 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -8,25 +8,18 @@ module ActiveRecord
end
def read_attribute_before_type_cast(attr_name)
- @attributes[attr_name]
+ _attributes.without_typecast[attr_name]
end
# Returns a hash of attributes before typecasting and deserialization.
def attributes_before_type_cast
- self.attribute_names.inject({}) do |attrs, name|
- attrs[name] = read_attribute_before_type_cast(name)
- attrs
- end
+ _attributes.without_typecast
end
private
# Handle *_before_type_cast for method_missing.
def 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
+ read_attribute_before_type_cast(attribute_name)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb
index a949d80120..0154ee35f8 100644
--- a/activerecord/lib/active_record/attribute_methods/query.rb
+++ b/activerecord/lib/active_record/attribute_methods/query.rb
@@ -8,23 +8,7 @@ module ActiveRecord
end
def query_attribute(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
+ _attributes.has?(attr_name)
end
private
@@ -35,3 +19,5 @@ module ActiveRecord
end
end
end
+
+
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 3da3d9d8cc..97caec7744 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -37,11 +37,7 @@ module ActiveRecord
protected
def define_method_attribute(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
+ define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name])
if attr_name == primary_key && attr_name != "id"
define_read_method(:id, attr_name, columns_hash[attr_name])
@@ -49,18 +45,12 @@ 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)
- cast_code = column.type_cast_code('v') if column
- access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
-
+ access_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.has_key?('#{attr_name}'); ")
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless _attributes.key?('#{attr_name}'); ")
end
if cache_attribute?(attr_name)
@@ -73,38 +63,7 @@ module ActiveRecord
# Returns the value of the attribute identified by attr_name 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)
- 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
+ _attributes[attr_name]
end
private
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index a8e3e28a7a..4ac0c7f608 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -12,48 +12,20 @@ module ActiveRecord
end
module ClassMethods
+
+ def cache_attribute?(attr_name)
+ time_zone_aware?(attr_name) || super
+ end
+
protected
- # 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
+ 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)
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
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index e31acac050..37eadbe0a9 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -17,14 +17,9 @@ module ActiveRecord
# Updates the attribute identified by attr_name with the specified +value+. Empty strings for fixnum and float
# columns are turned into +nil+.
def write_attribute(attr_name, value)
- attr_name = attr_name.to_s
- attr_name = self.class.primary_key if attr_name == 'id'
+ attr_name = _attributes.unalias(attr_name)
@attributes_cache.delete(attr_name)
- if (column = column_for_attribute(attr_name)) && column.number?
- @attributes[attr_name] = convert_number_column_value(value)
- else
- @attributes[attr_name] = value
- end
+ _attributes[attr_name] = value
end
private
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
new file mode 100644
index 0000000000..e4d9e89821
--- /dev/null
+++ b/activerecord/lib/active_record/attributes.rb
@@ -0,0 +1,37 @@
+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
diff --git a/activerecord/lib/active_record/attributes/aliasing.rb b/activerecord/lib/active_record/attributes/aliasing.rb
new file mode 100644
index 0000000000..db77739d1f
--- /dev/null
+++ b/activerecord/lib/active_record/attributes/aliasing.rb
@@ -0,0 +1,42 @@
+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
+
diff --git a/activerecord/lib/active_record/attributes/store.rb b/activerecord/lib/active_record/attributes/store.rb
new file mode 100644
index 0000000000..61109f4acc
--- /dev/null
+++ b/activerecord/lib/active_record/attributes/store.rb
@@ -0,0 +1,15 @@
+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
diff --git a/activerecord/lib/active_record/attributes/typecasting.rb b/activerecord/lib/active_record/attributes/typecasting.rb
new file mode 100644
index 0000000000..de36a297eb
--- /dev/null
+++ b/activerecord/lib/active_record/attributes/typecasting.rb
@@ -0,0 +1,111 @@
+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
+
+ # 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
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 76dbd00ad9..4274df54cc 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1644,7 +1644,7 @@ module ActiveRecord #:nodoc:
def instantiate(record)
object = find_sti_class(record[inheritance_column]).allocate
- object.instance_variable_set(:'@attributes', record)
+ object.send(:initialize_attribute_store, record)
object.instance_variable_set(:'@attributes_cache', {})
object.send(:_run_find_callbacks)
@@ -2415,7 +2415,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)
- @attributes = attributes_from_column_definition
+ initialize_attribute_store(attributes_from_column_definition)
@attributes_cache = {}
@new_record = true
ensure_proper_type
@@ -2441,7 +2441,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)
- @attributes = cloned_attributes
+ initialize_attribute_store(cloned_attributes)
clear_aggregation_cache
@attributes_cache = {}
@new_record = true
@@ -2667,7 +2667,7 @@ 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
@@ -2764,16 +2764,6 @@ module ActiveRecord #:nodoc:
!value.blank?
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 column object for the named attribute.
def column_for_attribute(name)
self.class.columns_hash[name.to_s]
@@ -2897,18 +2887,6 @@ module ActiveRecord #:nodoc:
end
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 remove_attributes_protected_from_mass_assignment(attributes)
safe_attributes =
if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
@@ -3027,7 +3005,7 @@ module ActiveRecord #:nodoc:
end
def instantiate_time_object(name, values)
- if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
+ if self.class.send(:time_zone_aware?, name)
Time.zone.local(*values)
else
Time.time_with_datetime_fallback(@@default_timezone, *values)
@@ -3114,10 +3092,6 @@ module ActiveRecord #:nodoc:
comma_pair_list(quote_columns(quoter, hash))
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
@@ -3132,6 +3106,7 @@ 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
@@ -3141,6 +3116,7 @@ module ActiveRecord #:nodoc:
include AutosaveAssociation, NestedAttributes
include Aggregations, Transactions, Reflection, Batches, Calculations, Serialization
+
end
end
diff --git a/activerecord/lib/active_record/types.rb b/activerecord/lib/active_record/types.rb
new file mode 100644
index 0000000000..74f569352b
--- /dev/null
+++ b/activerecord/lib/active_record/types.rb
@@ -0,0 +1,38 @@
+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
diff --git a/activerecord/lib/active_record/types/number.rb b/activerecord/lib/active_record/types/number.rb
new file mode 100644
index 0000000000..cfbe877575
--- /dev/null
+++ b/activerecord/lib/active_record/types/number.rb
@@ -0,0 +1,30 @@
+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
\ No newline at end of file
diff --git a/activerecord/lib/active_record/types/object.rb b/activerecord/lib/active_record/types/object.rb
new file mode 100644
index 0000000000..ec3f861abd
--- /dev/null
+++ b/activerecord/lib/active_record/types/object.rb
@@ -0,0 +1,37 @@
+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
\ No newline at end of file
diff --git a/activerecord/lib/active_record/types/serialize.rb b/activerecord/lib/active_record/types/serialize.rb
new file mode 100644
index 0000000000..7b6af1981f
--- /dev/null
+++ b/activerecord/lib/active_record/types/serialize.rb
@@ -0,0 +1,33 @@
+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
\ No newline at end of file
diff --git a/activerecord/lib/active_record/types/time_with_zone.rb b/activerecord/lib/active_record/types/time_with_zone.rb
new file mode 100644
index 0000000000..3a8b9292f9
--- /dev/null
+++ b/activerecord/lib/active_record/types/time_with_zone.rb
@@ -0,0 +1,20 @@
+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
diff --git a/activerecord/lib/active_record/types/unknown.rb b/activerecord/lib/active_record/types/unknown.rb
new file mode 100644
index 0000000000..f832c7b304
--- /dev/null
+++ b/activerecord/lib/active_record/types/unknown.rb
@@ -0,0 +1,37 @@
+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
\ No newline at end of file
diff --git a/activerecord/test/cases/attributes/aliasing_test.rb b/activerecord/test/cases/attributes/aliasing_test.rb
new file mode 100644
index 0000000000..7ee25779f1
--- /dev/null
+++ b/activerecord/test/cases/attributes/aliasing_test.rb
@@ -0,0 +1,20 @@
+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
diff --git a/activerecord/test/cases/attributes/typecasting_test.rb b/activerecord/test/cases/attributes/typecasting_test.rb
new file mode 100644
index 0000000000..c712f224b2
--- /dev/null
+++ b/activerecord/test/cases/attributes/typecasting_test.rb
@@ -0,0 +1,118 @@
+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 = @attributes.without_typecast
+ attributes['comments_count'] = '5'
+
+ assert_equal '5', attributes['comments_count']
+ 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
diff --git a/activerecord/test/cases/types/number_test.rb b/activerecord/test/cases/types/number_test.rb
new file mode 100644
index 0000000000..ee7216a0f1
--- /dev/null
+++ b/activerecord/test/cases/types/number_test.rb
@@ -0,0 +1,30 @@
+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
diff --git a/activerecord/test/cases/types/object_test.rb b/activerecord/test/cases/types/object_test.rb
new file mode 100644
index 0000000000..f2667a9b00
--- /dev/null
+++ b/activerecord/test/cases/types/object_test.rb
@@ -0,0 +1,24 @@
+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
diff --git a/activerecord/test/cases/types/serialize_test.rb b/activerecord/test/cases/types/serialize_test.rb
new file mode 100644
index 0000000000..e9423a5b9d
--- /dev/null
+++ b/activerecord/test/cases/types/serialize_test.rb
@@ -0,0 +1,20 @@
+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
\ No newline at end of file
diff --git a/activerecord/test/cases/types/time_with_zone_test.rb b/activerecord/test/cases/types/time_with_zone_test.rb
new file mode 100644
index 0000000000..b3de79a6c8
--- /dev/null
+++ b/activerecord/test/cases/types/time_with_zone_test.rb
@@ -0,0 +1,42 @@
+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
diff --git a/activerecord/test/cases/types/unknown_test.rb b/activerecord/test/cases/types/unknown_test.rb
new file mode 100644
index 0000000000..230d67b2fb
--- /dev/null
+++ b/activerecord/test/cases/types/unknown_test.rb
@@ -0,0 +1,29 @@
+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
\ No newline at end of file
diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb
new file mode 100644
index 0000000000..403a9a6e02
--- /dev/null
+++ b/activerecord/test/cases/types_test.rb
@@ -0,0 +1,32 @@
+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
--
cgit v1.2.3
From 03c5a0e5c4c9888c54265d6ef97136854e0ff9e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Sat, 17 Oct 2009 15:54:58 -0300
Subject: Make app generatoor specs green once again.
---
railties/lib/rails/generators/rails/app/templates/Gemfile | 6 +++---
railties/test/generators/app_generator_test.rb | 11 +++++++++--
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index abe8c1556c..3966c0f70d 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -1,9 +1,9 @@
# Gemfile is where you list all of your application's dependencies
#
-gem "rails", "<%= Rails::VERSION::STRING %>"
+<%= "# " if options.freeze? %>gem "rails", "<%= Rails::VERSION::STRING %>"
#
# Bundling edge rails:
-# gem "rails", "<%= Rails::VERSION::STRING %>", :git => "git://github.com/rails/rails.git"
+<%= "# " unless options.freeze? %>gem "rails", "<%= Rails::VERSION::STRING %>", :git => "git://github.com/rails/rails.git"
# Specify gemcutter as a gem source
# source "http://gemcutter.org"
@@ -18,4 +18,4 @@ gem "rails", "<%= Rails::VERSION::STRING %>"
# gem "rspec", :only => :test
# only :test do
# gem "webrat"
-# end
\ No newline at end of file
+# end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 5d6a9f6de9..20f2a24e6d 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -114,11 +114,18 @@ class AppGeneratorTest < GeneratorsTestCase
generator(:freeze => true, :database => "sqlite3").expects(:run).
with("rake rails:freeze:edge", :verbose => false)
silence(:stdout){ generator.invoke }
- assert_file 'config/environment.rb', /# RAILS_GEM_VERSION/
+
+ assert_file 'Gemfile' do |content|
+ flag = %(gem "rails", "#{Rails::VERSION::STRING}", :git => "git://github.com/rails/rails.git")
+ assert_match /^#{Regexp.escape(flag)}$/, content
+
+ flag = %(# gem "rails", "#{Rails::VERSION::STRING}")
+ assert_match /^#{Regexp.escape(flag)}$/, content
+ end
end
def test_template_from_dir_pwd
- FileUtils.cd(RAILS_ROOT)
+ FileUtils.cd(Rails.root)
assert_match /It works from file!/, run_generator(["-m", "lib/template.rb"])
end
--
cgit v1.2.3
From 1f9d234a6b567e68d97e71da6f19bd126e7f7058 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Sat, 17 Oct 2009 15:56:44 -0300
Subject: By default use ActiveModel API in controller generators, unless
otherwise specified [#3123 status:resolved]
---
railties/lib/rails/generators/active_model.rb | 18 ++++++----
railties/lib/rails/generators/active_record.rb | 34 ------------------
railties/lib/rails/generators/resource_helpers.rb | 17 +++------
.../scaffold_controller_generator_test.rb | 41 +++++++++++++++++++---
4 files changed, 53 insertions(+), 57 deletions(-)
diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb
index 1a849a0e02..fe6321af30 100644
--- a/railties/lib/rails/generators/active_model.rb
+++ b/railties/lib/rails/generators/active_model.rb
@@ -32,7 +32,7 @@ module Rails
# GET index
def self.all(klass)
- raise NotImplementedError
+ "#{klass}.all"
end
# GET show
@@ -40,34 +40,38 @@ module Rails
# PUT update
# DELETE destroy
def self.find(klass, params=nil)
- raise NotImplementedError
+ "#{klass}.find(#{params})"
end
# GET new
# POST create
def self.build(klass, params=nil)
- raise NotImplementedError
+ if params
+ "#{klass}.new(#{params})"
+ else
+ "#{klass}.new"
+ end
end
# POST create
def save
- raise NotImplementedError
+ "#{name}.save"
end
# PUT update
def update_attributes(params=nil)
- raise NotImplementedError
+ "#{name}.update_attributes(#{params})"
end
# POST create
# PUT update
def errors
- raise NotImplementedError
+ "#{name}.errors"
end
# DELETE destroy
def destroy
- raise NotImplementedError
+ "#{name}.destroy"
end
end
end
diff --git a/railties/lib/rails/generators/active_record.rb b/railties/lib/rails/generators/active_record.rb
index c03ea59c1b..babad33db3 100644
--- a/railties/lib/rails/generators/active_record.rb
+++ b/railties/lib/rails/generators/active_record.rb
@@ -18,39 +18,5 @@ module ActiveRecord
end
end
end
-
- class ActiveModel < Rails::Generators::ActiveModel #:nodoc:
- def self.all(klass)
- "#{klass}.all"
- end
-
- def self.find(klass, params=nil)
- "#{klass}.find(#{params})"
- end
-
- def self.build(klass, params=nil)
- if params
- "#{klass}.new(#{params})"
- else
- "#{klass}.new"
- end
- end
-
- def save
- "#{name}.save"
- end
-
- def update_attributes(params=nil)
- "#{name}.update_attributes(#{params})"
- end
-
- def errors
- "#{name}.errors"
- end
-
- def destroy
- "#{name}.destroy"
- end
- end
end
end
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index d4b0d4b945..0385581083 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -1,3 +1,5 @@
+require 'rails/generators/active_model'
+
module Rails
module Generators
# Deal with controller names on scaffold and add some helpers to deal with
@@ -47,20 +49,11 @@ module Rails
raise "You need to have :orm as class option to invoke orm_class and orm_instance"
end
- active_model = "#{options[:orm].to_s.classify}::Generators::ActiveModel"
-
- # If the orm was not loaded, try to load it at "generators/orm",
- # for example "generators/active_record" or "generators/sequel".
begin
- klass = active_model.constantize
- rescue NameError
- require "rails/generators/#{options[:orm]}"
+ "#{options[:orm].to_s.classify}::Generators::ActiveModel".constantize
+ rescue NameError => e
+ Rails::Generators::ActiveModel
end
-
- # Try once again after loading the file with success.
- klass ||= active_model.constantize
- rescue Exception => e
- raise Error, "Could not load #{active_model}, skipping controller. Error: #{e.message}."
end
end
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index f555725eb8..02155c295c 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -2,6 +2,11 @@ require 'abstract_unit'
require 'generators/generators_test_helper'
require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator'
+module Unknown
+ module Generators
+ end
+end
+
class ScaffoldControllerGeneratorTest < GeneratorsTestCase
def test_controller_skeleton_is_created
@@ -97,10 +102,38 @@ class ScaffoldControllerGeneratorTest < GeneratorsTestCase
assert_no_file "app/views/layouts/users.html.erb"
end
- def test_error_is_shown_if_orm_does_not_provide_interface
- error = capture(:stderr){ run_generator ["User", "--orm=unknown"] }
- assert_equal "Could not load Unknown::Generators::ActiveModel, skipping controller. " <<
- "Error: no such file to load -- rails/generators/unknown.\n", error
+ def test_default_orm_is_used
+ run_generator ["User", "--orm=unknown"]
+
+ assert_file "app/controllers/users_controller.rb" do |content|
+ assert_match /class UsersController < ApplicationController/, content
+
+ assert_instance_method content, :index do |m|
+ assert_match /@users = User\.all/, m
+ end
+ end
+ end
+
+ def test_customized_orm_is_used
+ klass = Class.new(Rails::Generators::ActiveModel) do
+ def self.all(klass)
+ "#{klass}.find(:all)"
+ end
+ end
+
+ Unknown::Generators.const_set(:ActiveModel, klass)
+ run_generator ["User", "--orm=unknown"]
+
+ assert_file "app/controllers/users_controller.rb" do |content|
+ assert_match /class UsersController < ApplicationController/, content
+
+ assert_instance_method content, :index do |m|
+ assert_match /@users = User\.find\(:all\)/, m
+ assert_no_match /@users = User\.all/, m
+ end
+ end
+ ensure
+ Unknown::Generators.send :remove_const, :ActiveModel
end
protected
--
cgit v1.2.3
From d0f4d93df823d5124d8f2cc740471d458633c338 Mon Sep 17 00:00:00 2001
From: Carl Lerche
Date: Sat, 17 Oct 2009 14:38:21 -0700
Subject: Remove some remnants of config.gem
---
railties/lib/rails/application.rb | 65 +-----
railties/lib/rails/configuration.rb | 21 +-
railties/lib/rails/deprecation.rb | 10 +-
.../rails/app/templates/config/application.rb | 6 -
railties/lib/rails/plugin/locator.rb | 2 +-
railties/test/gem_dependency_test.rb | 220 ---------------------
6 files changed, 16 insertions(+), 308 deletions(-)
delete mode 100644 railties/test/gem_dependency_test.rb
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 521e1cafb6..943c939757 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -311,31 +311,6 @@ module Rails
end
end
- initializer :check_for_unbuilt_gems do
- unbuilt_gems = config.gems.select {|gem| gem.frozen? && !gem.built? }
- if unbuilt_gems.size > 0
- # don't print if the gems:build rake tasks are being run
- unless $gems_build_rake_task
- abort <<-end_error
- The following gems have native components that need to be built
- #{unbuilt_gems.map { |gemm| "#{gemm.name} #{gemm.requirement}" } * "\n "}
-
- You're running:
- ruby #{Gem.ruby_version} at #{Gem.ruby}
- rubygems #{Gem::RubyGemsVersion} at #{Gem.path * ', '}
-
- Run `rake gems:build` to build the unbuilt gems.
- end_error
- end
- end
- end
-
- initializer :load_gems do
- unless $gems_rake_task
- config.gems.each { |gem| gem.load }
- end
- end
-
# Loads all plugins in config.plugin_paths. plugin_paths
# defaults to vendor/plugins but may also be set to a list of
# paths, such as
@@ -356,49 +331,19 @@ module Rails
plugin_loader.load_plugins
end
- # TODO: Figure out if this needs to run a second time
- # load_gems
-
- initializer :check_gem_dependencies do
- unloaded_gems = config.gems.reject { |g| g.loaded? }
- if unloaded_gems.size > 0
- configuration.gems_dependencies_loaded = false
- # don't print if the gems rake tasks are being run
- unless $gems_rake_task
- abort <<-end_error
- Missing these required gems:
- #{unloaded_gems.map { |gemm| "#{gemm.name} #{gemm.requirement}" } * "\n "}
-
- You're running:
- ruby #{Gem.ruby_version} at #{Gem.ruby}
- rubygems #{Gem::RubyGemsVersion} at #{Gem.path * ', '}
-
- Run `rake gems:install` to install the missing gems.
- end_error
- end
- else
- configuration.gems_dependencies_loaded = true
- end
- end
-
# # bail out if gems are missing - note that check_gem_dependencies will have
# # already called abort() unless $gems_rake_task is set
# return unless gems_dependencies_loaded
-
initializer :load_application_initializers do
- if config.gems_dependencies_loaded
- Dir["#{configuration.root}/config/initializers/**/*.rb"].sort.each do |initializer|
- load(initializer)
- end
+ Dir["#{configuration.root}/config/initializers/**/*.rb"].sort.each do |initializer|
+ load(initializer)
end
end
# Fires the user-supplied after_initialize block (Configuration#after_initialize)
initializer :after_initialize do
- if config.gems_dependencies_loaded
- configuration.after_initialize_blocks.each do |block|
- block.call
- end
+ configuration.after_initialize_blocks.each do |block|
+ block.call
end
end
@@ -440,7 +385,7 @@ module Rails
#
# # Observers are loaded after plugins in case Observers or observed models are modified by plugins.
initializer :load_observers do
- if config.gems_dependencies_loaded && configuration.frameworks.include?(:active_record)
+ if configuration.frameworks.include?(:active_record)
ActiveRecord::Base.instantiate_observers
end
end
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index ce9c899400..1a7483c548 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -4,7 +4,7 @@ require 'rails/plugin/locator'
module Rails
class Configuration
attr_accessor :cache_classes, :load_paths,
- :load_once_paths, :gems_dependencies_loaded, :after_initialize_blocks,
+ :load_once_paths, :after_initialize_blocks,
:frameworks, :framework_root_path, :root, :plugin_paths, :plugins,
:plugin_loader, :plugin_locators, :gems, :loaded_plugins, :reload_plugins,
:i18n, :gems, :whiny_nils, :consider_all_requests_local,
@@ -230,25 +230,6 @@ module Rails
end
end
- # Adds a single Gem dependency to the rails application. By default, it will require
- # the library with the same name as the gem. Use :lib to specify a different name.
- #
- # # gem 'aws-s3', '>= 0.4.0'
- # # require 'aws/s3'
- # config.gem 'aws-s3', :lib => 'aws/s3', :version => '>= 0.4.0', \
- # :source => "http://code.whytheluckystiff.net"
- #
- # To require a library be installed, but not attempt to load it, pass :lib => false
- #
- # config.gem 'qrp', :version => '0.4.1', :lib => false
- def gem(name, options = {})
- gems << Rails::GemDependency.new(name, options)
- end
-
- def gems
- @gems ||= []
- end
-
def environment_path
"#{root}/config/environments/#{RAILS_ENV}.rb"
end
diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb
index 42bba151d6..3c5b8bdec7 100644
--- a/railties/lib/rails/deprecation.rb
+++ b/railties/lib/rails/deprecation.rb
@@ -14,4 +14,12 @@ RAILS_ROOT = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do
msg = "RAILS_ROOT is deprecated! Use Rails.root instead."
ActiveSupport::Deprecation.warn(msg, callstack)
end
-end).new
\ No newline at end of file
+end).new
+
+module Rails
+ class Configuration
+ def gem(*args)
+ ActiveSupport::Deprecation.warn("config.gem has been deprecated in favor of the Gemfile.")
+ end
+ end
+end
\ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 7ba7c52f9e..bb30136686 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -8,12 +8,6 @@ Rails::Initializer.run do |config|
# Add additional load paths for your own custom dirs
# config.load_paths += %W( #{root}/extras )
- # Specify gems that this application depends on and have them installed with rake gems:install
- # config.gem "bj"
- # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
- # config.gem "sqlite3-ruby", :lib => "sqlite3"
- # config.gem "aws-s3", :lib => "aws/s3"
-
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
diff --git a/railties/lib/rails/plugin/locator.rb b/railties/lib/rails/plugin/locator.rb
index 1057c004e0..56cbaf37c6 100644
--- a/railties/lib/rails/plugin/locator.rb
+++ b/railties/lib/rails/plugin/locator.rb
@@ -78,7 +78,7 @@ module Rails
# a rails/init.rb file.
class GemLocator < Locator
def plugins
- gem_index = initializer.configuration.gems.inject({}) { |memo, gem| memo.update gem.specification => gem }
+ gem_index = {}
specs = gem_index.keys
specs += Gem.loaded_specs.values.select do |spec|
spec.loaded_from && # prune stubs
diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb
deleted file mode 100644
index 92132be992..0000000000
--- a/railties/test/gem_dependency_test.rb
+++ /dev/null
@@ -1,220 +0,0 @@
-require 'plugin_test_helper'
-require 'rails/gem_dependency'
-
-class Rails::GemDependency
- public :install_command, :unpack_command
-end
-
-Rails::VendorGemSourceIndex.silence_spec_warnings = true
-
-class GemDependencyTest < Test::Unit::TestCase
- def setup
- @gem = Rails::GemDependency.new "xhpricotx"
- @gem_with_source = Rails::GemDependency.new "xhpricotx", :source => "http://code.whytheluckystiff.net"
- @gem_with_version = Rails::GemDependency.new "xhpricotx", :version => "= 0.6"
- @gem_with_lib = Rails::GemDependency.new "xaws-s3x", :lib => "aws/s3"
- @gem_without_load = Rails::GemDependency.new "xhpricotx", :lib => false
- end
-
- def test_configuration_adds_gem_dependency
- config = Rails::Configuration.new
- config.gem "xaws-s3x", :lib => "aws/s3", :version => "0.4.0"
- assert_equal [["install", "xaws-s3x", "--version", '"= 0.4.0"']], config.gems.collect { |g| g.install_command }
- end
-
- def test_gem_creates_install_command
- assert_equal %w(install xhpricotx), @gem.install_command
- end
-
- def test_gem_with_source_creates_install_command
- assert_equal %w(install xhpricotx --source http://code.whytheluckystiff.net), @gem_with_source.install_command
- end
-
- def test_gem_with_version_creates_install_command
- assert_equal ["install", "xhpricotx", "--version", '"= 0.6"'], @gem_with_version.install_command
- end
-
- def test_gem_creates_unpack_command
- assert_equal %w(unpack xhpricotx), @gem.unpack_command
- end
-
- def test_gem_with_version_unpack_install_command
- # stub out specification method, or else test will fail if hpricot 0.6 isn't installed
- mock_spec = mock()
- mock_spec.stubs(:version).returns('0.6')
- @gem_with_version.stubs(:specification).returns(mock_spec)
- assert_equal ["unpack", "xhpricotx", "--version", '= 0.6'], @gem_with_version.unpack_command
- end
-
- def test_gem_adds_load_paths
- @gem.expects(:gem).with(@gem)
- @gem.add_load_paths
- end
-
- def test_gem_with_version_adds_load_paths
- @gem_with_version.expects(:gem).with(@gem_with_version)
- @gem_with_version.add_load_paths
- assert @gem_with_version.load_paths_added?
- end
-
- def test_gem_loading
- @gem.expects(:gem).with(@gem)
- @gem.expects(:require).with(@gem.name)
- @gem.add_load_paths
- @gem.load
- assert @gem.loaded?
- end
-
- def test_gem_with_lib_loading
- @gem_with_lib.expects(:gem).with(@gem_with_lib)
- @gem_with_lib.expects(:require).with(@gem_with_lib.lib)
- @gem_with_lib.add_load_paths
- @gem_with_lib.load
- assert @gem_with_lib.loaded?
- end
-
- def test_gem_without_lib_loading
- @gem_without_load.expects(:gem).with(@gem_without_load)
- @gem_without_load.expects(:require).with(@gem_without_load.lib).never
- @gem_without_load.add_load_paths
- @gem_without_load.load
- end
-
- def test_gem_dependencies_compare_for_uniq
- gem1 = Rails::GemDependency.new "gem1"
- gem1a = Rails::GemDependency.new "gem1"
- gem2 = Rails::GemDependency.new "gem2"
- gem2a = Rails::GemDependency.new "gem2"
- gem3 = Rails::GemDependency.new "gem2", :version => ">=0.1"
- gem3a = Rails::GemDependency.new "gem2", :version => ">=0.1"
- gem4 = Rails::GemDependency.new "gem3"
- gems = [gem1, gem2, gem1a, gem3, gem2a, gem4, gem3a, gem2, gem4]
- assert_equal 4, gems.uniq.size
- end
-
- def test_gem_load_frozen
- dummy_gem = Rails::GemDependency.new "dummy-gem-a"
- dummy_gem.add_load_paths
- dummy_gem.load
- assert_not_nil DUMMY_GEM_A_VERSION
- end
-
- def test_gem_load_frozen_specific_version
- dummy_gem = Rails::GemDependency.new "dummy-gem-b", :version => '0.4.0'
- dummy_gem.add_load_paths
- dummy_gem.load
- assert_not_nil DUMMY_GEM_B_VERSION
- assert_equal '0.4.0', DUMMY_GEM_B_VERSION
- end
-
- def test_gem_load_frozen_minimum_version
- dummy_gem = Rails::GemDependency.new "dummy-gem-c", :version => '>=0.5.0'
- dummy_gem.add_load_paths
- dummy_gem.load
- assert_not_nil DUMMY_GEM_C_VERSION
- assert_equal '0.6.0', DUMMY_GEM_C_VERSION
- end
-
- def test_gem_load_missing_specification
- dummy_gem = Rails::GemDependency.new "dummy-gem-d"
- dummy_gem.add_load_paths
- dummy_gem.load
- assert_not_nil DUMMY_GEM_D_VERSION
- assert_equal '1.0.0', DUMMY_GEM_D_VERSION
- assert_equal ['lib', 'lib/dummy-gem-d.rb'], dummy_gem.specification.files
- end
-
- def test_gem_load_bad_specification
- dummy_gem = Rails::GemDependency.new "dummy-gem-e", :version => "= 1.0.0"
- dummy_gem.add_load_paths
- dummy_gem.load
- assert_not_nil DUMMY_GEM_E_VERSION
- assert_equal '1.0.0', DUMMY_GEM_E_VERSION
- end
-
- def test_gem_handle_missing_dependencies
- dummy_gem = Rails::GemDependency.new "dummy-gem-g"
- dummy_gem.add_load_paths
- dummy_gem.load
- assert_equal 1, dummy_gem.dependencies.size
- assert_equal 1, dummy_gem.dependencies.first.dependencies.size
- assert_nothing_raised do
- dummy_gem.dependencies.each do |g|
- g.dependencies
- end
- end
- end
-
- def test_gem_ignores_development_dependencies
- dummy_gem = Rails::GemDependency.new "dummy-gem-k"
- dummy_gem.add_load_paths
- dummy_gem.load
- assert_equal 1, dummy_gem.dependencies.size
- end
-
- def test_gem_guards_against_duplicate_unpacks
- dummy_gem = Rails::GemDependency.new "dummy-gem-a"
- dummy_gem.stubs(:frozen?).returns(true)
- dummy_gem.expects(:unpack_base).never
- dummy_gem.unpack
- end
-
- def test_gem_does_not_unpack_framework_gems
- dummy_gem = Rails::GemDependency.new "dummy-gem-a"
- dummy_gem.stubs(:framework_gem?).returns(true)
- dummy_gem.expects(:unpack_base).never
- dummy_gem.unpack
- end
-
- def test_gem_from_directory_name_attempts_to_load_specification
- assert_raises RuntimeError do
- dummy_gem = Rails::GemDependency.from_directory_name('dummy-gem-1.1')
- end
- end
-
- def test_gem_from_directory_name
- dummy_gem = Rails::GemDependency.from_directory_name('dummy-gem-1.1', false)
- assert_equal 'dummy-gem', dummy_gem.name
- assert_equal '= 1.1', dummy_gem.version_requirements.to_s
- end
-
- def test_gem_from_directory_name_loads_specification_successfully
- assert_nothing_raised do
- dummy_gem = Rails::GemDependency.from_directory_name(File.join(Rails::GemDependency.unpacked_path, 'dummy-gem-g-1.0.0'))
- assert_not_nil dummy_gem.specification
- end
- end
-
- def test_gem_from_invalid_directory_name
- assert_raises RuntimeError do
- dummy_gem = Rails::GemDependency.from_directory_name('dummy-gem')
- end
- assert_raises RuntimeError do
- dummy_gem = Rails::GemDependency.from_directory_name('dummy')
- end
- end
-
- def test_gem_determines_build_status
- assert_equal true, Rails::GemDependency.new("dummy-gem-a").built?
- assert_equal true, Rails::GemDependency.new("dummy-gem-i").built?
- assert_equal false, Rails::GemDependency.new("dummy-gem-j").built?
- end
-
- def test_gem_determines_build_status_only_on_vendor_gems
- framework_gem = Rails::GemDependency.new('dummy-framework-gem')
- framework_gem.stubs(:framework_gem?).returns(true) # already loaded
- framework_gem.stubs(:vendor_rails?).returns(false) # but not in vendor/rails
- framework_gem.stubs(:vendor_gem?).returns(false) # and not in vendor/gems
- framework_gem.add_load_paths # freeze framework gem early
- assert framework_gem.built?
- end
-
- def test_gem_build_passes_options_to_dependencies
- start_gem = Rails::GemDependency.new("dummy-gem-g")
- dep_gem = Rails::GemDependency.new("dummy-gem-f")
- start_gem.stubs(:dependencies).returns([dep_gem])
- dep_gem.expects(:build).with({ :force => true }).once
- start_gem.build(:force => true)
- end
-
-end
--
cgit v1.2.3
From d50413826f1892b5d1250f578b283d52b85b4f6c Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 17 Oct 2009 16:36:39 -0500
Subject: Invalid route requirements should always raise an exception even if
they are unused
---
actionpack/test/controller/routing_test.rb | 8 --------
1 file changed, 8 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 7c88520bac..cd13f36681 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1019,14 +1019,6 @@ class RouteSetTest < ActiveSupport::TestCase
map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\z/
end
end
- assert_nothing_raised do
- set.draw do |map|
- map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/, :name => /^(david|jamis)/
- end
- assert_raise ActionController::RoutingError do
- set.generate :controller => 'pages', :action => 'show', :id => 10
- end
- end
end
def test_route_requirements_with_invalid_http_method_is_invalid
--
cgit v1.2.3
From 20f0b33035aeb1ca8ca7bdfb98371b58c6fd10c1 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 17 Oct 2009 16:39:16 -0500
Subject: Drop support for routing :generate_all
---
actionpack/test/controller/routing_test.rb | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index cd13f36681..345039f5b2 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1407,20 +1407,6 @@ class RouteSetTest < ActiveSupport::TestCase
{:controller => 'post', :action => 'show', :project_id => '1'})
end
- def test_generate_all
- set.draw do |map|
- map.connect 'show_post/:id', :controller => 'post', :action => 'show'
- map.connect ':controller/:action/:id'
- end
- all = set.generate(
- {:action => 'show', :id => 10, :generate_all => true},
- {:controller => 'post', :action => 'show'}
- )
- assert_equal 2, all.length
- assert_equal '/show_post/10', all.first
- assert_equal '/post/show/10', all.last
- end
-
def test_named_route_in_nested_resource
set.draw do |map|
map.resources :projects do |project|
--
cgit v1.2.3
From e00f57e20833a297efd1670890ebe5b030dbfdf1 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 17 Oct 2009 16:43:17 -0500
Subject: No longer need this special routing exception message because these
kinds of ambiguous generations are deprecated
---
actionpack/test/controller/routing_test.rb | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 345039f5b2..7aced7b6f5 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -425,19 +425,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
assert_no_match /:controller=>"post"/, diff_match
end
- # this specifies the case where your formerly would get a very confusing error message with an empty diff
- def test_should_have_better_error_message_when_options_diff_is_empty
- rs.draw do |map|
- map.content '/content/:query', :controller => 'content', :action => 'show'
- end
-
- exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'content', :action => 'show', :use_route => "content") }
- assert_match %r[:action=>"show"], exception.message
- assert_match %r[:controller=>"content"], exception.message
- assert_match %r[you may have ambiguous routes, or you may need to supply additional parameters for this route], exception.message
- assert_match %r[content_url has the following required parameters: \["content", :query\] - are they all satisfied?], exception.message
- end
-
def test_dynamic_path_allowed
rs.draw do |map|
map.connect '*path', :controller => 'content', :action => 'show_file'
--
cgit v1.2.3
From 6c2a73909ec71148cec60c46e11d57d40247bb63 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 17 Oct 2009 17:17:23 -0500
Subject: Stop using with_controllers in tests
---
actionpack/test/controller/routing_test.rb | 168 +++++++++++++----------------
1 file changed, 72 insertions(+), 96 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 7aced7b6f5..1ceb1ab68c 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -747,12 +747,9 @@ class RouteSetTest < ActiveSupport::TestCase
def default_route_set
@default_route_set ||= begin
- set = nil
- ActionController::Routing.with_controllers(['accounts']) do
- set = ROUTING::RouteSet.new
- set.draw do |map|
- map.connect '/:controller/:action/:id/'
- end
+ set = ROUTING::RouteSet.new
+ set.draw do |map|
+ map.connect '/:controller/:action/:id/'
end
set
end
@@ -940,44 +937,38 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_draw_default_route
- ActionController::Routing.with_controllers(['users']) do
- set.draw do |map|
- map.connect '/:controller/:action/:id'
- end
+ set.draw do |map|
+ map.connect '/:controller/:action/:id'
+ end
- assert_equal 1, set.routes.size
+ assert_equal 1, set.routes.size
- assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10)
- assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10)
+ assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10)
+ assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10)
- assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10'))
- assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/'))
- end
+ assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10'))
+ assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/'))
end
def test_draw_default_route_with_default_controller
- ActionController::Routing.with_controllers(['users']) do
- set.draw do |map|
- map.connect '/:controller/:action/:id', :controller => 'users'
- end
- assert_equal({:controller => 'users', :action => 'index'}, set.recognize_path('/'))
+ set.draw do |map|
+ map.connect '/:controller/:action/:id', :controller => 'users'
end
+ assert_equal({:controller => 'users', :action => 'index'}, set.recognize_path('/'))
end
def test_route_with_parameter_shell
- ActionController::Routing.with_controllers(['users', 'pages']) do
- set.draw do |map|
- map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
- map.connect '/:controller/:action/:id'
- end
+ set.draw do |map|
+ map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
+ map.connect '/:controller/:action/:id'
+ end
- assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages'))
- assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index'))
- assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list'))
+ assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages'))
+ assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index'))
+ assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list'))
- assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10'))
- assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10'))
- end
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10'))
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10'))
end
def test_route_requirements_with_anchor_chars_are_invalid
@@ -1568,101 +1559,86 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/named'))
end
-
def test_interpolation_chunk_should_respect_raw
- ActionController::Routing.with_controllers(['hello']) do
- set.draw do |map|
- map.connect '/Hello World', :controller => 'hello'
- end
-
- assert_equal '/Hello%20World', set.generate(:controller => 'hello')
- assert_equal({:controller => "hello", :action => "index"}, set.recognize_path('/Hello World'))
- assert_raise(ActionController::RoutingError) { set.recognize_path('/Hello%20World') }
+ set.draw do |map|
+ map.connect '/Hello World', :controller => 'hello'
end
+
+ assert_equal '/Hello%20World', set.generate(:controller => 'hello')
+ assert_equal({:controller => "hello", :action => "index"}, set.recognize_path('/Hello World'))
+ assert_raise(ActionController::RoutingError) { set.recognize_path('/Hello%20World') }
end
def test_value_should_not_be_double_unescaped
- ActionController::Routing.with_controllers(['foo']) do
- set.draw do |map|
- map.connect '/Карта', :controller => 'foo'
- end
-
- assert_equal '/%D0%9A%D0%B0%D1%80%D1%82%D0%B0', set.generate(:controller => 'foo')
- assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Карта'))
- assert_raise(ActionController::RoutingError) { set.recognize_path('/%D0%9A%D0%B0%D1%80%D1%82%D0%B0') }
+ set.draw do |map|
+ map.connect '/Карта', :controller => 'foo'
end
+
+ assert_equal '/%D0%9A%D0%B0%D1%80%D1%82%D0%B0', set.generate(:controller => 'foo')
+ assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Карта'))
+ assert_raise(ActionController::RoutingError) { set.recognize_path('/%D0%9A%D0%B0%D1%80%D1%82%D0%B0') }
end
def test_regexp_chunk_should_escape_specials
- ActionController::Routing.with_controllers(['foo', 'bar']) do
- set.draw do |map|
- map.connect '/Hello*World', :controller => 'foo'
- map.connect '/HelloWorld', :controller => 'bar'
- end
+ set.draw do |map|
+ map.connect '/Hello*World', :controller => 'foo'
+ map.connect '/HelloWorld', :controller => 'bar'
+ end
- assert_equal '/Hello*World', set.generate(:controller => 'foo')
- assert_equal '/HelloWorld', set.generate(:controller => 'bar')
+ assert_equal '/Hello*World', set.generate(:controller => 'foo')
+ assert_equal '/HelloWorld', set.generate(:controller => 'bar')
- assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Hello*World'))
- assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/HelloWorld'))
- end
+ assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Hello*World'))
+ assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/HelloWorld'))
end
def test_regexp_chunk_should_add_question_mark_for_optionals
- ActionController::Routing.with_controllers(['foo', 'bar']) do
- set.draw do |map|
- map.connect '/', :controller => 'foo'
- map.connect '/hello', :controller => 'bar'
- end
+ set.draw do |map|
+ map.connect '/', :controller => 'foo'
+ map.connect '/hello', :controller => 'bar'
+ end
- assert_equal '/', set.generate(:controller => 'foo')
- assert_equal '/hello', set.generate(:controller => 'bar')
+ assert_equal '/', set.generate(:controller => 'foo')
+ assert_equal '/hello', set.generate(:controller => 'bar')
- assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/'))
- assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/hello'))
- end
+ assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/'))
+ assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/hello'))
end
def test_assign_route_options_with_anchor_chars
- ActionController::Routing.with_controllers(['cars']) do
- set.draw do |map|
- map.connect '/cars/:action/:person/:car/', :controller => 'cars'
- end
+ set.draw do |map|
+ map.connect '/cars/:action/:person/:car/', :controller => 'cars'
+ end
- assert_equal '/cars/buy/1/2', set.generate(:controller => 'cars', :action => 'buy', :person => '1', :car => '2')
+ assert_equal '/cars/buy/1/2', set.generate(:controller => 'cars', :action => 'buy', :person => '1', :car => '2')
- assert_equal({:controller => "cars", :action => "buy", :person => "1", :car => "2"}, set.recognize_path('/cars/buy/1/2'))
- end
+ assert_equal({:controller => "cars", :action => "buy", :person => "1", :car => "2"}, set.recognize_path('/cars/buy/1/2'))
end
def test_segmentation_of_dot_path
- ActionController::Routing.with_controllers(['books']) do
- set.draw do |map|
- map.connect '/books/:action.rss', :controller => 'books'
- end
+ set.draw do |map|
+ map.connect '/books/:action.rss', :controller => 'books'
+ end
- assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list')
+ assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list')
- assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list.rss'))
- end
+ assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list.rss'))
end
def test_segmentation_of_dynamic_dot_path
- ActionController::Routing.with_controllers(['books']) do
- set.draw do |map|
- map.connect '/books/:action.:format', :controller => 'books'
- end
+ set.draw do |map|
+ map.connect '/books/:action.:format', :controller => 'books'
+ end
- assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list', :format => 'rss')
- assert_equal '/books/list.xml', set.generate(:controller => 'books', :action => 'list', :format => 'xml')
- assert_equal '/books/list', set.generate(:controller => 'books', :action => 'list')
- assert_equal '/books', set.generate(:controller => 'books', :action => 'index')
+ assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list', :format => 'rss')
+ assert_equal '/books/list.xml', set.generate(:controller => 'books', :action => 'list', :format => 'xml')
+ assert_equal '/books/list', set.generate(:controller => 'books', :action => 'list')
+ assert_equal '/books', set.generate(:controller => 'books', :action => 'index')
- assert_equal({:controller => "books", :action => "list", :format => "rss"}, set.recognize_path('/books/list.rss'))
- assert_equal({:controller => "books", :action => "list", :format => "xml"}, set.recognize_path('/books/list.xml'))
- assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list'))
- assert_equal({:controller => "books", :action => "index"}, set.recognize_path('/books'))
- end
+ assert_equal({:controller => "books", :action => "list", :format => "rss"}, set.recognize_path('/books/list.rss'))
+ assert_equal({:controller => "books", :action => "list", :format => "xml"}, set.recognize_path('/books/list.xml'))
+ assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list'))
+ assert_equal({:controller => "books", :action => "index"}, set.recognize_path('/books'))
end
def test_slashes_are_implied
--
cgit v1.2.3
From 702df0d2384609f1b0ee1cf3f068c823dc1b3a0d Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 17 Oct 2009 17:26:50 -0500
Subject: Fix standalone run of routing test. Only reference controllers that
are provided in fake_controllers
---
actionpack/test/controller/routing_test.rb | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 1ceb1ab68c..5f160c1464 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1345,20 +1345,20 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do |map|
map.connect ':controller/:action/:id'
end
- assert_equal '/post', set.generate(
- {:controller => 'post', :action => 'index'},
- {:controller => 'post', :action => 'show', :id => '10'}
+ assert_equal '/books', set.generate(
+ {:controller => 'books', :action => 'index'},
+ {:controller => 'books', :action => 'show', :id => '10'}
)
end
def test_query_params_will_be_shown_when_recalled
set.draw do |map|
- map.connect 'show_post/:parameter', :controller => 'post', :action => 'show'
+ map.connect 'show_weblog/:parameter', :controller => 'weblog', :action => 'show'
map.connect ':controller/:action/:id'
end
- assert_equal '/post/edit?parameter=1', set.generate(
+ assert_equal '/weblog/edit?parameter=1', set.generate(
{:action => 'edit', :parameter => 1},
- {:controller => 'post', :action => 'show', :parameter => 1}
+ {:controller => 'weblog', :action => 'show', :parameter => 1}
)
end
@@ -1380,9 +1380,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_expiry_determination_should_consider_values_with_to_param
set.draw { |map| map.connect 'projects/:project_id/:controller/:action' }
- assert_equal '/projects/1/post/show', set.generate(
+ assert_equal '/projects/1/weblog/show', set.generate(
{:action => 'show', :project_id => 1},
- {:controller => 'post', :action => 'show', :project_id => '1'})
+ {:controller => 'weblog', :action => 'show', :project_id => '1'})
end
def test_named_route_in_nested_resource
--
cgit v1.2.3
From e900a8437a6f1dcbf993dfbb1b82ee51a11128b4 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 17 Oct 2009 17:34:00 -0500
Subject: Fix brittle query string comparisons
---
actionpack/test/controller/routing_test.rb | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 5f160c1464..def5ff4957 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1723,32 +1723,43 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_build_empty_query_string
- assert_equal '/foo', default_route_set.generate({:controller => 'foo'})
+ assert_uri_equal '/foo', default_route_set.generate({:controller => 'foo'})
end
def test_build_query_string_with_nil_value
- assert_equal '/foo', default_route_set.generate({:controller => 'foo', :x => nil})
+ assert_uri_equal '/foo', default_route_set.generate({:controller => 'foo', :x => nil})
end
def test_simple_build_query_string
- assert_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => '1', :y => '2'})
+ assert_uri_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => '1', :y => '2'})
end
def test_convert_ints_build_query_string
- assert_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => 1, :y => 2})
+ assert_uri_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => 1, :y => 2})
end
def test_escape_spaces_build_query_string
- assert_equal '/foo?x=hello+world&y=goodbye+world', default_route_set.generate({:controller => 'foo', :x => 'hello world', :y => 'goodbye world'})
+ assert_uri_equal '/foo?x=hello+world&y=goodbye+world', default_route_set.generate({:controller => 'foo', :x => 'hello world', :y => 'goodbye world'})
end
def test_expand_array_build_query_string
- assert_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]})
+ assert_uri_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]})
end
def test_escape_spaces_build_query_string_selected_keys
- assert_equal '/foo?x=hello+world', default_route_set.generate({:controller => 'foo', :x => 'hello world'})
+ assert_uri_equal '/foo?x=hello+world', default_route_set.generate({:controller => 'foo', :x => 'hello world'})
end
+
+ private
+ def assert_uri_equal(expected, actual)
+ assert_equal(sort_query_string_params(expected), sort_query_string_params(actual))
+ end
+
+ def sort_query_string_params(uri)
+ path, qs = uri.split('?')
+ qs = qs.split('&').sort.join('&') if qs
+ qs ? "#{path}?#{qs}" : path
+ end
end
class RouteLoadingTest < Test::Unit::TestCase
--
cgit v1.2.3
From cc0103fe833d556e750fd040e34cf0165c3c7ccc Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 17 Oct 2009 19:18:18 -0500
Subject: Fix brittle query string comparisons
---
actionpack/test/controller/url_rewriter_test.rb | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index 4c4bf9ade4..d81ced96a8 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -321,8 +321,8 @@ class UrlWriterTests < ActionController::TestCase
params = extract_params(url)
assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
- assert_equal params[2], { 'query[person][position][]' => 'prof' }.to_query
- assert_equal params[3], { 'query[person][position][]' => 'art director' }.to_query
+ assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query
+ assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query
end
def test_path_generation_for_symbol_parameter_keys
@@ -359,10 +359,10 @@ class UrlWriterTests < ActionController::TestCase
controller = kls.new
params = {:id => 1, :format => :xml}
assert_deprecated do
- assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params))
+ assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params))
end
assert_deprecated do
- assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml))
+ assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml))
end
end
end
@@ -382,6 +382,6 @@ class UrlWriterTests < ActionController::TestCase
private
def extract_params(url)
- url.split('?', 2).last.split('&')
+ url.split('?', 2).last.split('&').sort
end
end
--
cgit v1.2.3
From f1767c1513172415d53f8aa347d9936f85560128 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 17 Oct 2009 21:21:34 -0500
Subject: Drop legacy support for case insensitive controller recognition
---
actionpack/test/controller/routing_test.rb | 11 -----------
1 file changed, 11 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index def5ff4957..3b70e7162c 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -490,17 +490,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
assert_equal '/content', rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
end
- def test_recognition_with_uppercase_controller_name
- @rs.draw {|m| m.connect ':controller/:action/:id' }
- assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/Content"))
- assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/ConTent/list"))
- assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/CONTENT/show/10"))
-
- # these used to work, before the routes rewrite, but support for this was pulled in the new version...
- #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/NewsFeed"))
- #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/News_Feed"))
- end
-
def test_requirement_should_prevent_optional_id
rs.draw do |map|
map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/}
--
cgit v1.2.3
From 2c3ca9ae80c0ec30ce1aede77c4dafc55bf2957e Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 17 Oct 2009 21:56:43 -0500
Subject: This is routing error message test is tightly coupled to the
implementation. Just test that it raises an exception.
---
actionpack/test/controller/routing_test.rb | 17 +----------------
1 file changed, 1 insertion(+), 16 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 3b70e7162c..83b26ba5a9 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -407,22 +407,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
rs.draw do |map|
map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/}
end
- exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") }
- assert_match /^post_url failed to generate/, exception.message
- from_match = exception.message.match(/from \{[^\}]+\}/).to_s
- assert_match /:bad_param=>"foo"/, from_match
- assert_match /:action=>"show"/, from_match
- assert_match /:controller=>"post"/, from_match
-
- expected_match = exception.message.match(/expected: \{[^\}]+\}/).to_s
- assert_no_match /:bad_param=>"foo"/, expected_match
- assert_match /:action=>"show"/, expected_match
- assert_match /:controller=>"post"/, expected_match
-
- diff_match = exception.message.match(/diff: \{[^\}]+\}/).to_s
- assert_match /:bad_param=>"foo"/, diff_match
- assert_no_match /:action=>"show"/, diff_match
- assert_no_match /:controller=>"post"/, diff_match
+ assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") }
end
def test_dynamic_path_allowed
--
cgit v1.2.3
From 6873b1d6589c0e8f1c29f156fb1841e165f3a127 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sun, 18 Oct 2009 00:26:36 -0500
Subject: Don't use use_controllers in routing tests
---
actionpack/test/controller/routing_test.rb | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 83b26ba5a9..8a9862a287 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -22,7 +22,6 @@ end
# See RFC 3986, section 3.3 for allowed path characters.
class UriReservedCharactersRoutingTest < Test::Unit::TestCase
def setup
- ActionController::Routing.use_controllers! ['controller']
@set = ActionController::Routing::RouteSet.new
@set.draw do |map|
map.connect ':controller/:action/:variable/*additional'
@@ -36,8 +35,9 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase
end
def test_route_generation_escapes_unsafe_path_characters
- assert_equal "/contr#{@segment}oller/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2",
- @set.generate(:controller => "contr#{@segment}oller",
+ @set.generate(:controller => "content", :action => "act#{@segment}ion", :variable => "variable", :additional => "foo")
+ assert_equal "/content/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2",
+ @set.generate(:controller => "content",
:action => "act#{@segment}ion",
:variable => "var#{@segment}iable",
:additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"])
@@ -52,7 +52,7 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase
end
def test_route_generation_allows_passing_non_string_values_to_generated_helper
- assert_equal "/controller/action/variable/1/2", @set.generate(:controller => "controller",
+ assert_equal "/content/action/variable/1/2", @set.generate(:controller => "content",
:action => "action",
:variable => "variable",
:additional => [1, 2])
--
cgit v1.2.3
From 2831996483c6a045f1f38d8030256eb58d9771c3 Mon Sep 17 00:00:00 2001
From: Eric Chapweske
Date: Sun, 18 Oct 2009 10:22:22 -0500
Subject: Fixed: #without_typecast should only disable typecasting on the
duplicated attributes [#3387 state:resolved]
Signed-off-by: Joshua Peek
---
activerecord/lib/active_record/attributes/typecasting.rb | 6 ++++++
activerecord/test/cases/attributes/typecasting_test.rb | 6 ++++--
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/activerecord/lib/active_record/attributes/typecasting.rb b/activerecord/lib/active_record/attributes/typecasting.rb
index de36a297eb..56c32f9895 100644
--- a/activerecord/lib/active_record/attributes/typecasting.rb
+++ b/activerecord/lib/active_record/attributes/typecasting.rb
@@ -37,6 +37,12 @@ module ActiveRecord
hash.merge!(self)
hash
end
+
+ def dup # :nodoc:
+ copy = super
+ copy.types = types.dup
+ copy
+ end
# Provides a duplicate with typecasting disabled.
#
diff --git a/activerecord/test/cases/attributes/typecasting_test.rb b/activerecord/test/cases/attributes/typecasting_test.rb
index c712f224b2..8a3b551375 100644
--- a/activerecord/test/cases/attributes/typecasting_test.rb
+++ b/activerecord/test/cases/attributes/typecasting_test.rb
@@ -86,12 +86,14 @@ class TypecastingTest < ActiveRecord::TestCase
end
test "without typecasting" do
+ @attributes.merge!('comments_count' => '5')
attributes = @attributes.without_typecast
- attributes['comments_count'] = '5'
-
+
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!
--
cgit v1.2.3
From 3ccaabc6c6e7cc86084820a10202589e240c81ad Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sun, 18 Oct 2009 10:39:21 -0500
Subject: Need to use "use_controllers" for nonexistent controller in AM test
---
actionmailer/test/url_test.rb | 44 ++++++++++++++++++++++---------------------
1 file changed, 23 insertions(+), 21 deletions(-)
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index 71286bd1cf..2224f6321c 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -1,9 +1,9 @@
require 'abstract_unit'
class TestMailer < ActionMailer::Base
-
+
default_url_options[:host] = 'www.basecamphq.com'
-
+
def signed_up_with_url(recipient)
@recipients = recipient
@subject = "[Signed up] Welcome #{recipient}"
@@ -52,25 +52,27 @@ class ActionMailerUrlTest < Test::Unit::TestCase
end
def test_signed_up_with_url
- ActionController::Routing::Routes.draw do |map|
- map.connect ':controller/:action/:id'
- map.welcome 'welcome', :controller=>"foo", :action=>"bar"
- end
+ 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"
- 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
+ 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"
+ 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
end
--
cgit v1.2.3
From f74e04c21d9930c863ef92639050c0434be8dd0c Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sun, 18 Oct 2009 10:44:05 -0500
Subject: RAILS_GEM_VERSION is obsolete
---
railties/test/generators/app_generator_test.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 5d6a9f6de9..e0b4e97952 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -114,7 +114,7 @@ class AppGeneratorTest < GeneratorsTestCase
generator(:freeze => true, :database => "sqlite3").expects(:run).
with("rake rails:freeze:edge", :verbose => false)
silence(:stdout){ generator.invoke }
- assert_file 'config/environment.rb', /# RAILS_GEM_VERSION/
+ assert_file 'config/environment.rb'
end
def test_template_from_dir_pwd
--
cgit v1.2.3
From 01e04a446c801af88a1bb0315efffc775a00eedf Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sun, 18 Oct 2009 10:53:43 -0500
Subject: Use Rails.root in railties tests
---
railties/test/generators/actions_test.rb | 2 +-
railties/test/generators/app_generator_test.rb | 2 +-
railties/test/generators_test.rb | 6 +++---
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index adc61f6d8a..199b5fa8b4 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -89,7 +89,7 @@ class ActionsTest < GeneratorsTestCase
def test_environment_should_include_data_in_environment_initializer_block
run_generator
- load_paths = 'config.load_paths += %w["#{RAILS_ROOT}/app/extras"]'
+ load_paths = 'config.load_paths += %w["#{Rails.root}/app/extras"]'
action :environment, load_paths
assert_file 'config/application.rb', /#{Regexp.escape(load_paths)}/
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index e0b4e97952..3eefaf9b02 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -118,7 +118,7 @@ class AppGeneratorTest < GeneratorsTestCase
end
def test_template_from_dir_pwd
- FileUtils.cd(RAILS_ROOT)
+ FileUtils.cd(Rails.root)
assert_match /It works from file!/, run_generator(["-m", "lib/template.rb"])
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 2e19169d3d..178b5ef6de 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -80,7 +80,7 @@ class GeneratorsTest < GeneratorsTestCase
Rails::Generators.instance_variable_set(:@load_paths, nil)
spec = Gem::Specification.new
- spec.expects(:full_gem_path).returns(File.join(RAILS_ROOT, 'vendor', 'another_gem_path', 'xspec'))
+ spec.expects(:full_gem_path).returns(File.join(Rails.root, 'vendor', 'another_gem_path', 'xspec'))
Gem.expects(:respond_to?).with(:loaded_specs).returns(true)
Gem.expects(:loaded_specs).returns(:spec => spec)
@@ -119,7 +119,7 @@ class GeneratorsTest < GeneratorsTestCase
end
def test_rails_root_templates
- template = File.join(RAILS_ROOT, "lib", "templates", "active_record", "model", "model.rb")
+ template = File.join(Rails.root, "lib", "templates", "active_record", "model", "model.rb")
# Create template
mkdir_p(File.dirname(template))
@@ -171,6 +171,6 @@ class GeneratorsTest < GeneratorsTestCase
def test_source_paths_for_not_namespaced_generators
mspec = Rails::Generators.find_by_namespace :mspec
- assert mspec.source_paths.include?(File.join(RAILS_ROOT, "lib", "templates", "mspec"))
+ assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "mspec"))
end
end
--
cgit v1.2.3
From 91726c201bf91b08dcb75f8d129d1002c489b79d Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sun, 18 Oct 2009 11:05:24 -0500
Subject: Relax generation requirements and only enforce the requirements used
in the path segment
---
actionpack/test/controller/routing_test.rb | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 8a9862a287..cbbd7e6951 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -997,19 +997,6 @@ class RouteSetTest < ActiveSupport::TestCase
end
end
- def test_non_path_route_requirements_match_all
- set.draw do |map|
- map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/
- end
- assert_equal '/page/37s', set.generate(:controller => 'pages', :action => 'show', :name => 'jamis')
- assert_raise ActionController::RoutingError do
- set.generate(:controller => 'pages', :action => 'show', :name => 'not_jamis')
- end
- assert_raise ActionController::RoutingError do
- set.generate(:controller => 'pages', :action => 'show', :name => 'nor_jamis_and_david')
- end
- end
-
def test_recognize_with_encoded_id_and_regex
set.draw do |map|
map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /[a-zA-Z0-9\+]+/
--
cgit v1.2.3
From 51e1260b182b7fd5fda0c3f6b97bb166a578d0e6 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sun, 18 Oct 2009 11:13:57 -0500
Subject: Rails info tests needs use_controllers
---
railties/test/rails_info_controller_test.rb | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index 99cf9168e1..a0484c0868 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -4,10 +4,6 @@ require 'action_controller'
require 'rails/info'
require 'rails/info_controller'
-ActionController::Routing::Routes.draw do |map|
- map.connect ':controller/:action/:id'
-end
-
module ActionController
class Base
include ActionController::Testing
@@ -18,9 +14,17 @@ class InfoControllerTest < ActionController::TestCase
tests Rails::InfoController
def setup
+ ActionController::Routing.use_controllers!(['rails/info'])
+ ActionController::Routing::Routes.draw do |map|
+ map.connect ':controller/:action/:id'
+ end
@controller.stubs(:consider_all_requests_local => false, :local_request? => true)
end
+ def teardown
+ ActionController::Routing.use_controllers! nil
+ end
+
test "info controller does not allow remote requests" do
@controller.stubs(:consider_all_requests_local => false, :local_request? => false)
get :properties
--
cgit v1.2.3
From 77bb129fdb3b1da8365931a6313b5e7ef4c91de0 Mon Sep 17 00:00:00 2001
From: Mike Gunderloy
Date: Wed, 23 Sep 2009 06:41:22 -0500
Subject: Fix bad assumption in BacktraceCleaner test [#3249 state:resolved]
Signed-off-by: Pratik Naik
---
railties/test/backtrace_cleaner_test.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb
index 0319d5f38c..64a47712b7 100644
--- a/railties/test/backtrace_cleaner_test.rb
+++ b/railties/test/backtrace_cleaner_test.rb
@@ -35,7 +35,7 @@ class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase
end
test "should format installed gems correctly" do
- @backtrace = [ "#{Gem.default_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
+ @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
@result = @cleaner.clean(@backtrace)
assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
end
--
cgit v1.2.3
From 1ac5cf478825391071d34ec3d7f294fe28c0fceb Mon Sep 17 00:00:00 2001
From: Pratik Naik
Date: Mon, 19 Oct 2009 15:06:54 -0200
Subject: Make sure boot.rb requires rubygems only when needed
---
.../rails/generators/rails/app/templates/config/boot.rb | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
index 3165a4091e..44c884623e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
@@ -1,15 +1,18 @@
# Package management
# Choose one
-# Use Bundler (preferred)
environment = File.expand_path('../../vendor/gems/environment', __FILE__)
-require environment if File.exist?(environment)
-
-# Use 2.x style vendor/rails directory
vendor_rails = File.expand_path('../../vendor/rails', __FILE__)
-Dir["#{vendor_rails}/*/lib"].each { |path| $:.unshift(path) } if File.exist?(vendor_rails)
-# Load Rails from traditional RubyGems
-require 'rubygems'
+if File.exist?(environment)
+ # Use Bundler (preferred)
+ require environment
+elsif File.exist?(vendor_rails)
+ # Use 2.x style vendor/rails directory
+ Dir["#{vendor_rails}/*/lib"].each { |path| $:.unshift(path) }
+else
+ # Load Rails from traditional RubyGems
+ require 'rubygems'
+end
require 'rails'
--
cgit v1.2.3
From 3072bf75557346f1e70c47baef0fba205bc39c51 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Mon, 19 Oct 2009 18:10:14 -0700
Subject: Check for bundler environment or 2.x-style vendor/rails + rubygems
---
.../generators/rails/app/templates/config/boot.rb | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
index 44c884623e..2c527b4a4a 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
@@ -1,17 +1,15 @@
-# Package management
-# Choose one
-
+# Use Bundler (preferred)
environment = File.expand_path('../../vendor/gems/environment', __FILE__)
-vendor_rails = File.expand_path('../../vendor/rails', __FILE__)
-
if File.exist?(environment)
- # Use Bundler (preferred)
require environment
-elsif File.exist?(vendor_rails)
- # Use 2.x style vendor/rails directory
- Dir["#{vendor_rails}/*/lib"].each { |path| $:.unshift(path) }
+
+# Use 2.x style vendor/rails and RubyGems
else
- # Load Rails from traditional RubyGems
+ vendor_rails = File.expand_path('../../vendor/rails', __FILE__)
+ if File.exist?(vendor_rails)
+ Dir["#{vendor_rails}/*/lib"].each { |path| $:.unshift(path) }
+ end
+
require 'rubygems'
end
--
cgit v1.2.3
From 4afe70790cd26e6b8f17106d031c67ce4da3efbe Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Mon, 19 Oct 2009 18:12:29 -0700
Subject: Check for environment.rb
---
railties/lib/rails/generators/rails/app/templates/config/boot.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
index 2c527b4a4a..5aa49ca5e6 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
@@ -1,6 +1,6 @@
# Use Bundler (preferred)
environment = File.expand_path('../../vendor/gems/environment', __FILE__)
-if File.exist?(environment)
+if File.exist?("#{environment}.rb")
require environment
# Use 2.x style vendor/rails and RubyGems
--
cgit v1.2.3
From 66c4f28bd63d4c4cecc6ecf88259792fd4aa5fcf Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Mon, 19 Oct 2009 18:33:38 -0700
Subject: Bump arel requirement to 0.1.1
---
activerecord/activerecord.gemspec | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index bae027bd2a..204cddde47 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', '= 3.0.pre')
s.add_dependency('activemodel', '= 3.0.pre')
- s.add_dependency('arel', '~> 0.1.0')
+ s.add_dependency('arel', '~> 0.1.1')
s.require_path = 'lib'
s.autorequire = 'active_record'
--
cgit v1.2.3
From 7ab30599a6c0ca44d68ca10383b07ba4a8bd75b4 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Mon, 19 Oct 2009 18:33:57 -0700
Subject: Error message references application.rb instead of environment.rb
---
railties/lib/rails/tasks/databases.rake | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/railties/lib/rails/tasks/databases.rake b/railties/lib/rails/tasks/databases.rake
index 70b59625f8..a35a6c156b 100644
--- a/railties/lib/rails/tasks/databases.rake
+++ b/railties/lib/rails/tasks/databases.rake
@@ -295,7 +295,7 @@ namespace :db do
if File.exists?(file)
load(file)
else
- abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/environment.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]}
+ abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]}
end
end
end
--
cgit v1.2.3
From 418ce487c4a41df2c3a3c1485107f32eb94fa493 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Mon, 19 Oct 2009 18:46:59 -0700
Subject: Only undef to_json if it's defined
---
actionpack/lib/action_controller/metal/responder.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index a16ed97131..c6e847ba0f 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -95,7 +95,7 @@ module ActionController #:nodoc:
delegate :get?, :post?, :put?, :delete?, :to => :request
# Undefine :to_json since it's defined on Object
- undef_method :to_json
+ undef_method(:to_json) if method_defined?(:to_json)
# Initializes a new responder an invoke the proper format. If the format is
# not defined, call to_format.
--
cgit v1.2.3
From c9cd10c4fa77aedb9116105d4d3c4d5684af4e1c Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Mon, 19 Oct 2009 19:00:17 -0700
Subject: Bundle arel for AR integration tests
---
actionpack/Gemfile | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/actionpack/Gemfile b/actionpack/Gemfile
index c236169b20..b3ffeaecb6 100644
--- a/actionpack/Gemfile
+++ b/actionpack/Gemfile
@@ -6,13 +6,12 @@ gem "rack", "1.0.1", :git => "git://github.com/rails/rack.git", :branch => "rack
gem "rack-test", "~> 0.5.0"
gem "activesupport", "3.0.pre", :vendored_at => rails_root.join("activesupport")
gem "activemodel", "3.0.pre", :vendored_at => rails_root.join("activemodel")
+gem "arel", :git => "git://github.com/rails/arel.git"
gem "activerecord", "3.0.pre", :vendored_at => rails_root.join("activerecord")
gem "erubis", "~> 2.6.0"
-only :test do
- gem "mocha"
- gem "sqlite3-ruby"
- gem "RedCloth"
-end
+gem "mocha"
+gem "sqlite3-ruby"
+gem "RedCloth"
disable_system_gems
--
cgit v1.2.3
From 27670363926ee341078aa69ae27204d7338037f5 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Mon, 19 Oct 2009 19:00:48 -0700
Subject: Use bundled env for tests only
---
activerecord/Gemfile | 6 ++----
activerecord/lib/active_record.rb | 13 ++++---------
activerecord/test/cases/helper.rb | 8 +++++++-
3 files changed, 13 insertions(+), 14 deletions(-)
diff --git a/activerecord/Gemfile b/activerecord/Gemfile
index 9cf37b0af0..3201e65f02 100644
--- a/activerecord/Gemfile
+++ b/activerecord/Gemfile
@@ -3,8 +3,6 @@ sibling = "#{File.dirname(__FILE__)}/.."
gem "activesupport", "3.0.pre", :vendored_at => "#{sibling}/activesupport"
gem "activemodel", "3.0.pre", :vendored_at => "#{sibling}/activemodel"
-gem "arel", :git => "git://github.com/rails/arel.git", :branch => 'master'
+gem "arel", :git => "git://github.com/rails/arel.git"
-only :test do
- gem "mocha"
-end
+gem "mocha"
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 8f118a6057..88becfb482 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -21,16 +21,11 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-bundled = "#{File.dirname(__FILE__)}/../vendor/gems/environment"
-if File.exist?("#{bundled}.rb")
- require bundled
-else
- activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
- $:.unshift(activesupport_path) if File.directory?(activesupport_path)
+activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
+$:.unshift(activesupport_path) if File.directory?(activesupport_path)
- activemodel_path = "#{File.dirname(__FILE__)}/../../activemodel/lib"
- $:.unshift(activemodel_path) if File.directory?(activemodel_path)
-end
+activemodel_path = "#{File.dirname(__FILE__)}/../../activemodel/lib"
+$:.unshift(activemodel_path) if File.directory?(activemodel_path)
require 'active_support'
require 'active_model'
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index aa09c7061f..63014c0297 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -1,5 +1,11 @@
$:.unshift(File.dirname(__FILE__) + '/../../lib')
-$:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib')
+
+bundled = "#{File.dirname(__FILE__)}/../../vendor/gems/environment"
+if File.exist?("#{bundled}.rb")
+ require bundled
+else
+ $:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib')
+end
require 'config'
--
cgit v1.2.3
From 4f6d8ceb0436cf7eea435bdfed87ecf5aba050c1 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Mon, 19 Oct 2009 19:22:23 -0700
Subject: Bundle for railties tests too
---
ci/ci_build.rb | 2 +-
railties/test/abstract_unit.rb | 18 +++++++++---------
railties/test/isolation/abstract_unit.rb | 32 ++++++++++++++++++++------------
3 files changed, 30 insertions(+), 22 deletions(-)
diff --git a/ci/ci_build.rb b/ci/ci_build.rb
index 3b06c9c7ce..6574481c19 100755
--- a/ci/ci_build.rb
+++ b/ci/ci_build.rb
@@ -80,7 +80,7 @@ cd "#{root_dir}/railties" do
puts
puts "[CruiseControl] Building RailTies"
puts
- build_results[:railties] = system 'rake'
+ build_results[:railties] = system 'gem bundle && rake'
end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 8010481609..7977b45a57 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -1,14 +1,14 @@
ORIG_ARGV = ARGV.dup
-require 'rubygems'
-gem 'rack', '~> 1.0.0'
-gem 'rack-test', '~> 0.5.0'
+bundled = "#{File.dirname(__FILE__)}/../vendor/gems/environment"
+if File.exist?("#{bundled}.rb")
+ require bundled
+else
+ %w(activesupport activemodel activerecord actionpack actionmailer activeresource).each do |lib|
+ $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../#{lib}/lib"
+ end
+end
-$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib"
-$:.unshift File.dirname(__FILE__) + "/../../activerecord/lib"
-$:.unshift File.dirname(__FILE__) + "/../../actionpack/lib"
-$:.unshift File.dirname(__FILE__) + "/../../actionmailer/lib"
-$:.unshift File.dirname(__FILE__) + "/../../activeresource/lib"
$:.unshift File.dirname(__FILE__) + "/../lib"
$:.unshift File.dirname(__FILE__) + "/../builtin/rails_info"
@@ -25,4 +25,4 @@ require 'rails'
Rails::Initializer.run do |config|
config.root = File.dirname(__FILE__)
-end
\ No newline at end of file
+end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index aafc9f68bb..557292e7d3 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -114,18 +114,26 @@ module TestHelpers
end
def boot_rails
- %w(
- actionmailer/lib
- actionpack/lib
- activemodel/lib
- activerecord/lib
- activeresource/lib
- activesupport/lib
- railties/lib
- railties
- ).reverse_each do |path|
- path = File.expand_path("../../../../#{path}", __FILE__)
- $:.unshift(path)
+ bundled = "#{File.dirname(__FILE__)}/../../vendor/gems/environment"
+ if File.exist?("#{bundled}.rb")
+ require bundled
+ %w(railties railties/lib).each do |path|
+ $LOAD_PATH.unshift File.expand_path("../../../../#{path}", __FILE__)
+ end
+ else
+ %w(
+ actionmailer/lib
+ actionpack/lib
+ activemodel/lib
+ activerecord/lib
+ activeresource/lib
+ activesupport/lib
+ railties/lib
+ railties
+ ).reverse_each do |path|
+ path = File.expand_path("../../../../#{path}", __FILE__)
+ $:.unshift(path)
+ end
end
end
end
--
cgit v1.2.3
From 16d245e79645954c20cb0a11ddaf447dc1ba1744 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Mon, 19 Oct 2009 19:25:52 -0700
Subject: Missed Gemfile commits
---
actionmailer/Gemfile | 9 +++++++++
railties/Gemfile | 12 ++++++++++++
2 files changed, 21 insertions(+)
create mode 100644 actionmailer/Gemfile
create mode 100644 railties/Gemfile
diff --git a/actionmailer/Gemfile b/actionmailer/Gemfile
new file mode 100644
index 0000000000..5dec362d57
--- /dev/null
+++ b/actionmailer/Gemfile
@@ -0,0 +1,9 @@
+Gem.sources.each { |uri| source uri }
+sibling = "#{File.dirname(__FILE__)}/.."
+
+gem "activesupport", "3.0.pre", :vendored_at => "#{sibling}/activesupport"
+gem "activemodel", "3.0.pre", :vendored_at => "#{sibling}/activemodel"
+gem "actionpack", "3.0.pre", :vendored_at => "#{sibling}/actionpack"
+gem "mocha"
+
+#disable_system_gems
diff --git a/railties/Gemfile b/railties/Gemfile
new file mode 100644
index 0000000000..f7e7b3ffc0
--- /dev/null
+++ b/railties/Gemfile
@@ -0,0 +1,12 @@
+Gem.sources.each { |uri| source uri }
+sibling = "#{File.dirname(__FILE__)}/.."
+
+gem "mocha"
+
+gem "arel", :git => "git://github.com/rails/arel.git"
+gem "rack", "1.0.1", :git => "git://github.com/rails/rack.git", :branch => "rack-1.0"
+gem "rack-test", "~> 0.5.0"
+
+%w(activesupport activemodel actionpack actionmailer activerecord activeresource).each do |lib|
+ gem lib, '3.0.pre', :vendored_at => "#{sibling}/#{lib}"
+end
--
cgit v1.2.3
From 33258d713a4bc20b71e92fd656c923a7b189cd33 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Mon, 19 Oct 2009 22:40:28 -0500
Subject: Fix brittle query string comparisons
---
actionpack/test/template/url_helper_test.rb | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index cec53e479c..bf0b4ad3a7 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -26,7 +26,7 @@ class UrlHelperTest < ActionView::TestCase
assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => true)
assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => false)
end
-
+
def test_url_for_escaping_is_safety_aware
assert url_for(:a => 'b', :c => 'd', :escape => true).html_safe?, "escaped urls should be html_safe?"
assert !url_for(:a => 'b', :c => 'd', :escape => false).html_safe?, "non-escaped urls shouldn't be safe"
@@ -54,7 +54,7 @@ class UrlHelperTest < ActionView::TestCase
path = @view.url_for(:controller => :cheeses, :foo => :bar, :baz => :quux)
- assert_equal '/cheeses?baz=quux&foo=bar', path
+ assert_equal '/cheeses?baz=quux&foo=bar', sort_query_string_params(path)
end
# todo: missing test cases
@@ -284,21 +284,21 @@ class UrlHelperTest < ActionView::TestCase
assert current_page?({ :action => "show", :controller => "weblog" })
assert current_page?("http://www.example.com/weblog/show")
end
-
+
def test_current_page_ignoring_params
@controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1")
@controller.url = "http://www.example.com/weblog/show?order=desc&page=1"
assert current_page?({ :action => "show", :controller => "weblog" })
assert current_page?("http://www.example.com/weblog/show")
end
-
+
def test_current_page_with_params_that_match
@controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1")
@controller.url = "http://www.example.com/weblog/show?order=desc&page=1"
assert current_page?({ :action => "show", :controller => "weblog", :order => "desc", :page => "1" })
assert current_page?("http://www.example.com/weblog/show?order=desc&page=1")
end
-
+
def test_link_unless_current
@controller.request = RequestMock.new("http://www.example.com/weblog/show")
@controller.url = "http://www.example.com/weblog/show"
@@ -378,10 +378,17 @@ class UrlHelperTest < ActionView::TestCase
assert_dom_equal "", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
end
-
+
def protect_against_forgery?
false
end
+
+ private
+ def sort_query_string_params(uri)
+ path, qs = uri.split('?')
+ qs = qs.split('&').sort.join('&') if qs
+ qs ? "#{path}?#{qs}" : path
+ end
end
class UrlHelperController < ActionController::Base
--
cgit v1.2.3
From 6c581f5fd57dc1706d5f674b4371c07d040c4151 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Mon, 19 Oct 2009 22:41:58 -0500
Subject: Kill routing tests expecting path to be unescaped. Most rack servers
already escape PATH_INFO.
---
actionpack/test/controller/routing_test.rb | 62 ++++++------------------------
1 file changed, 12 insertions(+), 50 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index cbbd7e6951..67448e66b9 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -27,7 +27,7 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase
map.connect ':controller/:action/:variable/*additional'
end
- safe, unsafe = %w(: @ & = + $ , ;), %w(^ / ? # [ ])
+ safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ])
hex = unsafe.map { |char| '%' + char.unpack('H2').first.upcase }
@segment = "#{safe.join}#{unsafe.join}".freeze
@@ -366,10 +366,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
results = rs.recognize_path "/file/hello%20world/how%20are%20you%3F"
assert results, "Recognition should have succeeded"
assert_equal ['hello world', 'how are you?'], results[:path]
-
- results = rs.recognize_path "/file"
- assert results, "Recognition should have succeeded"
- assert_equal [], results[:path]
end
def test_paths_slashes_unescaped_with_ordered_parameters
@@ -379,7 +375,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
# No / to %2F in URI, only for query params.
x = setup_for_named_route
- assert_equal("/file/hello/world", x.send(:path_path, 'hello/world'))
+ assert_equal("/file/hello/world", x.send(:path_path, ['hello', 'world']))
end
def test_non_controllers_cannot_be_matched
@@ -1234,16 +1230,16 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "/foo/bar/baz/7", url
end
- def test_id_is_not_impossibly_sticky
- set.draw do |map|
- map.connect 'foo/:number', :controller => "people", :action => "index"
- map.connect ':controller/:action/:id'
- end
-
- url = set.generate({:controller => "people", :action => "index", :number => 3},
- {:controller => "people", :action => "index", :id => "21"})
- assert_equal "/foo/3", url
- end
+ # def test_id_is_not_impossibly_sticky
+ # set.draw do |map|
+ # map.connect 'foo/:number', :controller => "people", :action => "index"
+ # map.connect ':controller/:action/:id'
+ # end
+ #
+ # url = set.generate({:controller => "people", :action => "index", :number => 3},
+ # {:controller => "people", :action => "index", :id => "21"})
+ # assert_equal "/foo/3", url
+ # end
def test_id_is_sticky_when_it_ought_to_be
set.draw do |map|
@@ -1520,39 +1516,6 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/named'))
end
- def test_interpolation_chunk_should_respect_raw
- set.draw do |map|
- map.connect '/Hello World', :controller => 'hello'
- end
-
- assert_equal '/Hello%20World', set.generate(:controller => 'hello')
- assert_equal({:controller => "hello", :action => "index"}, set.recognize_path('/Hello World'))
- assert_raise(ActionController::RoutingError) { set.recognize_path('/Hello%20World') }
- end
-
- def test_value_should_not_be_double_unescaped
- set.draw do |map|
- map.connect '/Карта', :controller => 'foo'
- end
-
- assert_equal '/%D0%9A%D0%B0%D1%80%D1%82%D0%B0', set.generate(:controller => 'foo')
- assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Карта'))
- assert_raise(ActionController::RoutingError) { set.recognize_path('/%D0%9A%D0%B0%D1%80%D1%82%D0%B0') }
- end
-
- def test_regexp_chunk_should_escape_specials
- set.draw do |map|
- map.connect '/Hello*World', :controller => 'foo'
- map.connect '/HelloWorld', :controller => 'bar'
- end
-
- assert_equal '/Hello*World', set.generate(:controller => 'foo')
- assert_equal '/HelloWorld', set.generate(:controller => 'bar')
-
- assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Hello*World'))
- assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/HelloWorld'))
- end
-
def test_regexp_chunk_should_add_question_mark_for_optionals
set.draw do |map|
map.connect '/', :controller => 'foo'
@@ -1654,7 +1617,6 @@ class RouteSetTest < ActiveSupport::TestCase
def test_default_route_should_uri_escape_pluses
expected = { :controller => 'pages', :action => 'show', :id => 'hello world' }
- assert_equal expected, default_route_set.recognize_path('/pages/show/hello world')
assert_equal expected, default_route_set.recognize_path('/pages/show/hello%20world')
assert_equal '/pages/show/hello%20world', default_route_set.generate(expected, expected)
--
cgit v1.2.3
From 248d84f2e6d5948cc88a5735c6685e7ed81abb6b Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Mon, 19 Oct 2009 20:58:55 -0700
Subject: Simplify path expansions
---
railties/lib/rails/generators/rails/app/templates/Rakefile | 2 +-
railties/lib/rails/generators/rails/app/templates/config/application.rb | 2 +-
railties/lib/rails/generators/rails/app/templates/config/environment.rb | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile
index 2450a927f0..6b6d07e8cc 100755
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile
@@ -1,7 +1,7 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
-require File.expand_path(File.join(File.dirname(__FILE__), 'config', 'application'))
+require File.expand_path('../config/application', __FILE__)
require 'rake'
require 'rake/testtask'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index bb30136686..8008c6ba07 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -1,4 +1,4 @@
-require File.expand_path(File.join(File.dirname(__FILE__), 'boot'))
+require File.expand_path('../boot', __FILE__)
Rails::Initializer.run do |config|
# Settings in config/environments/* take precedence over those specified here.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
index e5362b21cc..0bb191f205 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
@@ -1,5 +1,5 @@
# Load the rails application
-require File.expand_path(File.join(File.dirname(__FILE__), 'application'))
+require File.expand_path('../application', __FILE__)
# Initialize the rails application
Rails.initialize!
--
cgit v1.2.3
From cbedcb06152ed6d7e7457334cd45af5ab24ef6ea Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Mon, 19 Oct 2009 21:03:37 -0700
Subject: Missed cherry-pick from 2e37effd7203cad84459661e11db2be44586cb4f
---
.../lib/active_support/core_ext/class/inheritable_attributes.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
index d8e9768a5e..e4d22516c1 100644
--- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/object/duplicable'
+require 'active_support/core_ext/array/extract_options'
# Retain for backward compatibility. Methods are now included in Class.
module ClassInheritableAttributes # :nodoc:
--
cgit v1.2.3
From a1df2590744ed126981dfd5b5709ff6fd5dc6476 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Mon, 19 Oct 2009 23:32:06 -0500
Subject: Replace decaying routing internals w/ rack-mount
---
actionpack/Gemfile | 1 +
actionpack/lib/action_controller.rb | 6 +-
actionpack/lib/action_controller/routing.rb | 18 +-
.../lib/action_controller/routing/builder.rb | 199 ----------
.../routing/generation/polymorphic_routes.rb | 210 ----------
.../routing/generation/url_rewriter.rb | 204 ----------
.../lib/action_controller/routing/optimisations.rb | 130 ------
.../routing/polymorphic_routes.rb | 210 ++++++++++
.../routing/recognition_optimisation.rb | 167 --------
actionpack/lib/action_controller/routing/route.rb | 267 -------------
.../lib/action_controller/routing/route_set.rb | 434 +++++++++++++++------
.../lib/action_controller/routing/routing_ext.rb | 4 -
.../lib/action_controller/routing/segments.rb | 343 ----------------
.../lib/action_controller/routing/url_rewriter.rb | 204 ++++++++++
.../lib/action_controller/testing/test_case.rb | 9 +-
actionpack/test/controller/routing_test.rb | 27 --
.../lib/active_support/core_ext/regexp.rb | 22 --
activesupport/test/core_ext/regexp_ext_test.rb | 19 -
18 files changed, 755 insertions(+), 1719 deletions(-)
delete mode 100644 actionpack/lib/action_controller/routing/builder.rb
delete mode 100644 actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb
delete mode 100644 actionpack/lib/action_controller/routing/generation/url_rewriter.rb
delete mode 100644 actionpack/lib/action_controller/routing/optimisations.rb
create mode 100644 actionpack/lib/action_controller/routing/polymorphic_routes.rb
delete mode 100644 actionpack/lib/action_controller/routing/recognition_optimisation.rb
delete mode 100644 actionpack/lib/action_controller/routing/route.rb
delete mode 100644 actionpack/lib/action_controller/routing/routing_ext.rb
delete mode 100644 actionpack/lib/action_controller/routing/segments.rb
create mode 100644 actionpack/lib/action_controller/routing/url_rewriter.rb
diff --git a/actionpack/Gemfile b/actionpack/Gemfile
index b3ffeaecb6..af95766608 100644
--- a/actionpack/Gemfile
+++ b/actionpack/Gemfile
@@ -3,6 +3,7 @@ rails_root = Pathname.new(File.dirname(__FILE__)).join("..")
Gem.sources.each { |uri| source uri }
gem "rack", "1.0.1", :git => "git://github.com/rails/rack.git", :branch => "rack-1.0"
+gem "rack-mount", :git => "git://github.com/josh/rack-mount.git"
gem "rack-test", "~> 0.5.0"
gem "activesupport", "3.0.pre", :vendored_at => rails_root.join("activesupport")
gem "activemodel", "3.0.pre", :vendored_at => rails_root.join("activemodel")
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 6702cb47f8..809e5f2ad4 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -26,14 +26,14 @@ module ActionController
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
autoload :MimeResponds, 'action_controller/metal/mime_responds'
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
- autoload :PolymorphicRoutes, 'action_controller/routing/generation/polymorphic_routes'
+ autoload :PolymorphicRoutes, 'action_controller/routing/polymorphic_routes'
autoload :RecordIdentifier, 'action_controller/record_identifier'
autoload :Resources, 'action_controller/routing/resources'
autoload :SessionManagement, 'action_controller/metal/session_management'
autoload :TestCase, 'action_controller/testing/test_case'
autoload :TestProcess, 'action_controller/testing/process'
- autoload :UrlRewriter, 'action_controller/routing/generation/url_rewriter'
- autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter'
+ autoload :UrlRewriter, 'action_controller/routing/url_rewriter'
+ autoload :UrlWriter, 'action_controller/routing/url_rewriter'
autoload :Verification, 'action_controller/metal/verification'
autoload :Flash, 'action_controller/metal/flash'
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb
index 5b9ded83dd..979a4ad8c9 100644
--- a/actionpack/lib/action_controller/routing.rb
+++ b/actionpack/lib/action_controller/routing.rb
@@ -1,16 +1,8 @@
-require 'cgi'
-require 'uri'
-require 'set'
-
-require 'active_support/core_ext/module/aliasing'
-require 'active_support/core_ext/module/attribute_accessors'
-require 'action_controller/routing/optimisations'
-require 'action_controller/routing/routing_ext'
-require 'action_controller/routing/route'
-require 'action_controller/routing/segments'
-require 'action_controller/routing/builder'
+require 'active_support/core_ext/object/conversions'
+require 'active_support/core_ext/boolean/conversions'
+require 'active_support/core_ext/nil/conversions'
+require 'active_support/core_ext/regexp'
require 'action_controller/routing/route_set'
-require 'action_controller/routing/recognition_optimisation'
module ActionController
# == Routing
@@ -197,7 +189,7 @@ module ActionController
#
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
#
- # will glob all remaining parts of the route that were not recognized earlier.
+ # will glob all remaining parts of the route that were not recognized earlier.
# The globbed values are in params[:path] as an array of path segments.
#
# == Route conditions
diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb
deleted file mode 100644
index 42ad12e1ea..0000000000
--- a/actionpack/lib/action_controller/routing/builder.rb
+++ /dev/null
@@ -1,199 +0,0 @@
-require 'active_support/core_ext/hash/except'
-
-module ActionController
- module Routing
- class RouteBuilder #:nodoc:
- attr_reader :separators, :optional_separators
- attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp
-
- def initialize
- @separators = Routing::SEPARATORS
- @optional_separators = %w( / )
-
- @separator_regexp = /[#{Regexp.escape(separators.join)}]/
- @nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/
- @interval_regexp = /(.*?)(#{separator_regexp}|$)/
- end
-
- # Accepts a "route path" (a string defining a route), and returns the array
- # of segments that corresponds to it. Note that the segment array is only
- # partially initialized--the defaults and requirements, for instance, need
- # to be set separately, via the +assign_route_options+ method, and the
- # optional? method for each segment will not be reliable until after
- # +assign_route_options+ is called, as well.
- def segments_for_route_path(path)
- rest, segments = path, []
-
- until rest.empty?
- segment, rest = segment_for(rest)
- segments << segment
- end
- segments
- end
-
- # A factory method that returns a new segment instance appropriate for the
- # format of the given string.
- def segment_for(string)
- segment =
- case string
- when /\A\.(:format)?\//
- OptionalFormatSegment.new
- when /\A:(\w+)/
- key = $1.to_sym
- key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key)
- when /\A\*(\w+)/
- PathSegment.new($1.to_sym, :optional => true)
- when /\A\?(.*?)\?/
- StaticSegment.new($1, :optional => true)
- when nonseparator_regexp
- StaticSegment.new($1)
- when separator_regexp
- DividerSegment.new($&, :optional => optional_separators.include?($&))
- end
- [segment, $~.post_match]
- end
-
- # Split the given hash of options into requirement and default hashes. The
- # segments are passed alongside in order to distinguish between default values
- # and requirements.
- def divide_route_options(segments, options)
- options = options.except(:path_prefix, :name_prefix)
-
- if options[:namespace]
- options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
- end
-
- requirements = (options.delete(:requirements) || {}).dup
- defaults = (options.delete(:defaults) || {}).dup
- conditions = (options.delete(:conditions) || {}).dup
-
- validate_route_conditions(conditions)
-
- path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
- options.each do |key, value|
- hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
- hash[key] = value
- end
-
- [defaults, requirements, conditions]
- end
-
- # Takes a hash of defaults and a hash of requirements, and assigns them to
- # the segments. Any unused requirements (which do not correspond to a segment)
- # are returned as a hash.
- def assign_route_options(segments, defaults, requirements)
- route_requirements = {} # Requirements that do not belong to a segment
-
- segment_named = Proc.new do |key|
- segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
- end
-
- requirements.each do |key, requirement|
- segment = segment_named[key]
- if segment
- raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
- if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
- end
- if requirement.multiline?
- raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
- end
- segment.regexp = requirement
- else
- route_requirements[key] = requirement
- end
- end
-
- defaults.each do |key, default|
- segment = segment_named[key]
- raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
- segment.is_optional = true
- segment.default = default.to_param if default
- end
-
- assign_default_route_options(segments)
- ensure_required_segments(segments)
- route_requirements
- end
-
- # Assign default options, such as 'index' as a default for :action. This
- # method must be run *after* user supplied requirements and defaults have
- # been applied to the segments.
- def assign_default_route_options(segments)
- segments.each do |segment|
- next unless segment.is_a? DynamicSegment
- case segment.key
- when :action
- if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index'
- segment.default ||= 'index'
- segment.is_optional = true
- end
- when :id
- if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ ''
- segment.is_optional = true
- end
- end
- end
- end
-
- # Makes sure that there are no optional segments that precede a required
- # segment. If any are found that precede a required segment, they are
- # made required.
- def ensure_required_segments(segments)
- allow_optional = true
- segments.reverse_each do |segment|
- allow_optional &&= segment.optional?
- if !allow_optional && segment.optional?
- unless segment.optionality_implied?
- warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
- end
- segment.is_optional = false
- elsif allow_optional && segment.respond_to?(:default) && segment.default
- # if a segment has a default, then it is optional
- segment.is_optional = true
- end
- end
- end
-
- # Construct and return a route with the given path and options.
- def build(path, options)
- # Wrap the path with slashes
- path = "/#{path}" unless path[0] == ?/
- path = "#{path}/" unless path[-1] == ?/
-
- prefix = options[:path_prefix].to_s.gsub(/^\//,'')
- path = "/#{prefix}#{path}" unless prefix.blank?
-
- segments = segments_for_route_path(path)
- defaults, requirements, conditions = divide_route_options(segments, options)
- requirements = assign_route_options(segments, defaults, requirements)
-
- # TODO: Segments should be frozen on initialize
- segments.each { |segment| segment.freeze }
-
- route = Route.new(segments, requirements, conditions)
-
- if !route.significant_keys.include?(:controller)
- raise ArgumentError, "Illegal route: the :controller must be specified!"
- end
-
- route.freeze
- end
-
- private
- def validate_route_conditions(conditions)
- if method = conditions[:method]
- [method].flatten.each do |m|
- if m == :head
- raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
- end
-
- unless HTTP_METHODS.include?(m.to_sym)
- raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
- end
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb
deleted file mode 100644
index 2adf3575a7..0000000000
--- a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-module ActionController
- # Polymorphic URL helpers are methods for smart resolution to a named route call when
- # given an Active Record model instance. They are to be used in combination with
- # ActionController::Resources.
- #
- # These methods are useful when you want to generate correct URL or path to a RESTful
- # resource without having to know the exact type of the record in question.
- #
- # Nested resources and/or namespaces are also supported, as illustrated in the example:
- #
- # polymorphic_url([:admin, @article, @comment])
- #
- # results in:
- #
- # admin_article_comment_url(@article, @comment)
- #
- # == Usage within the framework
- #
- # Polymorphic URL helpers are used in a number of places throughout the Rails framework:
- #
- # * url_for, so you can use it with a record as the argument, e.g.
- # url_for(@article);
- # * ActionView::Helpers::FormHelper uses polymorphic_path, so you can write
- # form_for(@article) without having to specify :url parameter for the form
- # action;
- # * redirect_to (which, in fact, uses url_for) so you can write
- # redirect_to(post) in your controllers;
- # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
- # for feed entries.
- #
- # == Prefixed polymorphic helpers
- #
- # In addition to polymorphic_url and polymorphic_path methods, a
- # number of prefixed helpers are available as a shorthand to :action => "..."
- # in options. Those are:
- #
- # * edit_polymorphic_url, edit_polymorphic_path
- # * new_polymorphic_url, new_polymorphic_path
- #
- # Example usage:
- #
- # edit_polymorphic_path(@post) # => "/posts/1/edit"
- # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
- module PolymorphicRoutes
- # Constructs a call to a named RESTful route for the given record and returns the
- # resulting URL string. For example:
- #
- # # calls post_url(post)
- # polymorphic_url(post) # => "http://example.com/posts/1"
- # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
- # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
- # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
- # polymorphic_url(Comment) # => "http://example.com/comments"
- #
- # ==== Options
- #
- # * :action - Specifies the action prefix for the named route:
- # :new or :edit. Default is no prefix.
- # * :routing_type - Allowed values are :path or :url.
- # Default is :url.
- #
- # ==== Examples
- #
- # # an Article record
- # polymorphic_url(record) # same as article_url(record)
- #
- # # a Comment record
- # polymorphic_url(record) # same as comment_url(record)
- #
- # # it recognizes new records and maps to the collection
- # record = Comment.new
- # polymorphic_url(record) # same as comments_url()
- #
- # # the class of a record will also map to the collection
- # polymorphic_url(Comment) # same as comments_url()
- #
- def polymorphic_url(record_or_hash_or_array, options = {})
- if record_or_hash_or_array.kind_of?(Array)
- record_or_hash_or_array = record_or_hash_or_array.compact
- record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
- end
-
- record = extract_record(record_or_hash_or_array)
- record = record.to_model if record.respond_to?(:to_model)
- namespace = extract_namespace(record_or_hash_or_array)
-
- args = case record_or_hash_or_array
- when Hash; [ record_or_hash_or_array ]
- when Array; record_or_hash_or_array.dup
- else [ record_or_hash_or_array ]
- end
-
- inflection = if options[:action].to_s == "new"
- args.pop
- :singular
- elsif (record.respond_to?(:new_record?) && record.new_record?) ||
- (record.respond_to?(:destroyed?) && record.destroyed?)
- args.pop
- :plural
- elsif record.is_a?(Class)
- args.pop
- :plural
- else
- :singular
- end
-
- args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
-
- named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
-
- url_options = options.except(:action, :routing_type)
- unless url_options.empty?
- args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
- end
-
- __send__(named_route, *args)
- end
-
- # Returns the path component of a URL for the given record. It uses
- # polymorphic_url with :routing_type => :path.
- def polymorphic_path(record_or_hash_or_array, options = {})
- polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
- end
-
- %w(edit new).each do |action|
- module_eval <<-EOT, __FILE__, __LINE__
- def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
- end # end
- #
- def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
- end # end
- EOT
- end
-
- def formatted_polymorphic_url(record_or_hash, options = {})
- ActiveSupport::Deprecation.warn("formatted_polymorphic_url has been deprecated. Please pass :format to the polymorphic_url method instead", caller)
- options[:format] = record_or_hash.pop if Array === record_or_hash
- polymorphic_url(record_or_hash, options)
- end
-
- def formatted_polymorphic_path(record_or_hash, options = {})
- ActiveSupport::Deprecation.warn("formatted_polymorphic_path has been deprecated. Please pass :format to the polymorphic_path method instead", caller)
- options[:format] = record_or_hash.pop if record_or_hash === Array
- polymorphic_url(record_or_hash, options.merge(:routing_type => :path))
- end
-
- private
- def action_prefix(options)
- options[:action] ? "#{options[:action]}_" : ''
- end
-
- def routing_type(options)
- options[:routing_type] || :url
- end
-
- def build_named_route_call(records, namespace, inflection, options = {})
- unless records.is_a?(Array)
- record = extract_record(records)
- route = ''
- else
- record = records.pop
- route = records.inject("") do |string, parent|
- if parent.is_a?(Symbol) || parent.is_a?(String)
- string << "#{parent}_"
- else
- string << "#{RecordIdentifier.__send__("plural_class_name", parent)}".singularize
- string << "_"
- end
- end
- end
-
- if record.is_a?(Symbol) || record.is_a?(String)
- route << "#{record}_"
- else
- route << "#{RecordIdentifier.__send__("plural_class_name", record)}"
- route = route.singularize if inflection == :singular
- route << "_"
- end
-
- action_prefix(options) + namespace + route + routing_type(options).to_s
- end
-
- def extract_record(record_or_hash_or_array)
- case record_or_hash_or_array
- when Array; record_or_hash_or_array.last
- when Hash; record_or_hash_or_array[:id]
- else record_or_hash_or_array
- end
- end
-
- # Remove the first symbols from the array and return the url prefix
- # implied by those symbols.
- def extract_namespace(record_or_hash_or_array)
- return "" unless record_or_hash_or_array.is_a?(Array)
-
- namespace_keys = []
- while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol)
- namespace_keys << record_or_hash_or_array.shift
- end
-
- namespace_keys.map {|k| "#{k}_"}.join
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb b/actionpack/lib/action_controller/routing/generation/url_rewriter.rb
deleted file mode 100644
index 52b66c9303..0000000000
--- a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb
+++ /dev/null
@@ -1,204 +0,0 @@
-module ActionController
- # In routes.rb one defines URL-to-controller mappings, but the reverse
- # is also possible: an URL can be generated from one of your routing definitions.
- # URL generation functionality is centralized in this module.
- #
- # See ActionController::Routing and ActionController::Resources for general
- # information about routing and routes.rb.
- #
- # Tip: If you need to generate URLs from your models or some other place,
- # then ActionController::UrlWriter is what you're looking for. Read on for
- # an introduction.
- #
- # == URL generation from parameters
- #
- # As you may know, some functions - such as ActionController::Base#url_for
- # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
- # of parameters. For example, you've probably had the chance to write code
- # like this in one of your views:
- #
- # <%= link_to('Click here', :controller => 'users',
- # :action => 'new', :message => 'Welcome!') %>
- #
- # #=> Generates a link to: /users/new?message=Welcome%21
- #
- # link_to, and all other functions that require URL generation functionality,
- # actually use ActionController::UrlWriter under the hood. And in particular,
- # they use the ActionController::UrlWriter#url_for method. One can generate
- # the same path as the above example by using the following code:
- #
- # include UrlWriter
- # url_for(:controller => 'users',
- # :action => 'new',
- # :message => 'Welcome!',
- # :only_path => true)
- # # => "/users/new?message=Welcome%21"
- #
- # Notice the :only_path => true part. This is because UrlWriter has no
- # information about the website hostname that your Rails app is serving. So if you
- # want to include the hostname as well, then you must also pass the :host
- # argument:
- #
- # include UrlWriter
- # url_for(:controller => 'users',
- # :action => 'new',
- # :message => 'Welcome!',
- # :host => 'www.example.com') # Changed this.
- # # => "http://www.example.com/users/new?message=Welcome%21"
- #
- # By default, all controllers and views have access to a special version of url_for,
- # that already knows what the current hostname is. So if you use url_for in your
- # controllers or your views, then you don't need to explicitly pass the :host
- # argument.
- #
- # For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
- # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
- # in full. However, mailers don't have hostname information, and what's why you'll still
- # have to specify the :host argument when generating URLs in mailers.
- #
- #
- # == URL generation for named routes
- #
- # UrlWriter also allows one to access methods that have been auto-generated from
- # named routes. For example, suppose that you have a 'users' resource in your
- # routes.rb:
- #
- # map.resources :users
- #
- # This generates, among other things, the method users_path. By default,
- # this method is accessible from your controllers, views and mailers. If you need
- # to access this auto-generated method from other places (such as a model), then
- # you can do that by including ActionController::UrlWriter in your class:
- #
- # class User < ActiveRecord::Base
- # include ActionController::UrlWriter
- #
- # def base_uri
- # user_path(self)
- # end
- # end
- #
- # User.find(1).base_uri # => "/users/1"
- module UrlWriter
- def self.included(base) #:nodoc:
- ActionController::Routing::Routes.install_helpers(base)
- base.mattr_accessor :default_url_options
-
- # The default options for urls written by this writer. Typically a :host pair is provided.
- base.default_url_options ||= {}
- end
-
- # Generate a url based on the options provided, default_url_options and the
- # routes defined in routes.rb. The following options are supported:
- #
- # * :only_path - If true, the relative url is returned. Defaults to +false+.
- # * :protocol - The protocol to connect to. Defaults to 'http'.
- # * :host - Specifies the host the link should be targeted at.
- # If :only_path is false, this option must be
- # provided either explicitly, or via +default_url_options+.
- # * :port - Optionally specify the port to connect to.
- # * :anchor - An anchor name to be appended to the path.
- # * :skip_relative_url_root - If true, the url is not constructed using the
- # +relative_url_root+ set in ActionController::Base.relative_url_root.
- # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2009/"
- #
- # Any other key (:controller, :action, etc.) given to
- # +url_for+ is forwarded to the Routes module.
- #
- # Examples:
- #
- # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
- # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
- # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
- # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
- def url_for(options)
- options = self.class.default_url_options.merge(options)
-
- url = ''
-
- unless options.delete(:only_path)
- url << (options.delete(:protocol) || 'http')
- url << '://' unless url.match("://")
-
- raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
-
- url << options.delete(:host)
- url << ":#{options.delete(:port)}" if options.key?(:port)
- else
- # Delete the unused options to prevent their appearance in the query string.
- [:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
- end
- trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
- url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
- anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
- generated = Routing::Routes.generate(options, {})
- url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
- url << anchor if anchor
-
- url
- end
- end
-
- # Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
- class UrlRewriter #:nodoc:
- RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
- def initialize(request, parameters)
- @request, @parameters = request, parameters
- end
-
- def rewrite(options = {})
- rewrite_url(options)
- end
-
- def to_str
- "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}"
- end
-
- alias_method :to_s, :to_str
-
- private
- # Given a path and options, returns a rewritten URL string
- def rewrite_url(options)
- rewritten_url = ""
-
- unless options[:only_path]
- rewritten_url << (options[:protocol] || @request.protocol)
- rewritten_url << "://" unless rewritten_url.match("://")
- rewritten_url << rewrite_authentication(options)
- rewritten_url << (options[:host] || @request.host_with_port)
- rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
- end
-
- path = rewrite_path(options)
- rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
- rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
- rewritten_url << "##{CGI.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
-
- rewritten_url
- end
-
- # Given a Hash of options, generates a route
- def rewrite_path(options)
- options = options.symbolize_keys
- options.update(options[:params].symbolize_keys) if options[:params]
-
- if (overwrite = options.delete(:overwrite_params))
- options.update(@parameters.symbolize_keys)
- options.update(overwrite.symbolize_keys)
- end
-
- RESERVED_OPTIONS.each { |k| options.delete(k) }
-
- # Generates the query string, too
- Routing::Routes.generate(options, @request.symbolized_path_parameters)
- end
-
- def rewrite_authentication(options)
- if options[:user] && options[:password]
- "#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@"
- else
- ""
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb
deleted file mode 100644
index 714cf97861..0000000000
--- a/actionpack/lib/action_controller/routing/optimisations.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-module ActionController
- module Routing
- # Much of the slow performance from routes comes from the
- # complexity of expiry, :requirements matching, defaults providing
- # and figuring out which url pattern to use. With named routes
- # we can avoid the expense of finding the right route. So if
- # they've provided the right number of arguments, and have no
- # :requirements, we can just build up a string and return it.
- #
- # To support building optimisations for other common cases, the
- # generation code is separated into several classes
- module Optimisation
- def generate_optimisation_block(route, kind)
- return "" unless route.optimise?
- OPTIMISERS.inject("") do |memo, klazz|
- memo << klazz.new(route, kind).source_code
- memo
- end
- end
-
- class Optimiser
- attr_reader :route, :kind
- GLOBAL_GUARD_CONDITIONS = [
- "(!defined?(default_url_options) || default_url_options.blank?)",
- "(!defined?(controller.default_url_options) || controller.default_url_options.blank?)",
- "defined?(request)",
- "request"
- ]
-
- def initialize(route, kind)
- @route = route
- @kind = kind
- end
-
- def guard_conditions
- ["false"]
- end
-
- def generation_code
- 'nil'
- end
-
- def source_code
- if applicable?
- guard_condition = (GLOBAL_GUARD_CONDITIONS + guard_conditions).join(" && ")
- "return #{generation_code} if #{guard_condition}\n"
- else
- "\n"
- end
- end
-
- # Temporarily disabled :url optimisation pending proper solution to
- # Issues around request.host etc.
- def applicable?
- true
- end
- end
-
- # Given a route
- #
- # map.person '/people/:id'
- #
- # If the user calls person_url(@person), we can simply
- # return a string like "/people/#{@person.to_param}"
- # rather than triggering the expensive logic in +url_for+.
- class PositionalArguments < Optimiser
- def guard_conditions
- number_of_arguments = route.required_segment_keys.size
- # if they're using foo_url(:id=>2) it's one
- # argument, but we don't want to generate /foos/id2
- if number_of_arguments == 1
- ["args.size == 1", "!args.first.is_a?(Hash)"]
- else
- ["args.size == #{number_of_arguments}"]
- end
- end
-
- def generation_code
- elements = []
- idx = 0
-
- if kind == :url
- elements << '#{request.protocol}'
- elements << '#{request.host_with_port}'
- end
-
- elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}'
-
- # The last entry in route.segments appears to *always* be a
- # 'divider segment' for '/' but we have assertions to ensure that
- # we don't include the trailing slashes, so skip them.
- (route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment|
- if segment.is_a?(DynamicSegment)
- elements << segment.interpolation_chunk("args[#{idx}].to_param")
- idx += 1
- else
- elements << segment.interpolation_chunk
- end
- end
- %("#{elements * ''}")
- end
- end
-
- # This case is mostly the same as the positional arguments case
- # above, but it supports additional query parameters as the last
- # argument
- class PositionalArgumentsWithAdditionalParams < PositionalArguments
- def guard_conditions
- ["args.size == #{route.segment_keys.size + 1}"] +
- UrlRewriter::RESERVED_OPTIONS.collect{ |key| "!args.last.has_key?(:#{key})" }
- end
-
- # This case uses almost the same code as positional arguments,
- # but add a question mark and args.last.to_query on the end,
- # unless the last arg is empty
- def generation_code
- super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}')
- end
-
- # To avoid generating "http://localhost/?host=foo.example.com" we
- # can't use this optimisation on routes without any segments
- def applicable?
- super && route.segment_keys.size > 0
- end
- end
-
- OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams]
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/polymorphic_routes.rb b/actionpack/lib/action_controller/routing/polymorphic_routes.rb
new file mode 100644
index 0000000000..2adf3575a7
--- /dev/null
+++ b/actionpack/lib/action_controller/routing/polymorphic_routes.rb
@@ -0,0 +1,210 @@
+module ActionController
+ # Polymorphic URL helpers are methods for smart resolution to a named route call when
+ # given an Active Record model instance. They are to be used in combination with
+ # ActionController::Resources.
+ #
+ # These methods are useful when you want to generate correct URL or path to a RESTful
+ # resource without having to know the exact type of the record in question.
+ #
+ # Nested resources and/or namespaces are also supported, as illustrated in the example:
+ #
+ # polymorphic_url([:admin, @article, @comment])
+ #
+ # results in:
+ #
+ # admin_article_comment_url(@article, @comment)
+ #
+ # == Usage within the framework
+ #
+ # Polymorphic URL helpers are used in a number of places throughout the Rails framework:
+ #
+ # * url_for, so you can use it with a record as the argument, e.g.
+ # url_for(@article);
+ # * ActionView::Helpers::FormHelper uses polymorphic_path, so you can write
+ # form_for(@article) without having to specify :url parameter for the form
+ # action;
+ # * redirect_to (which, in fact, uses url_for) so you can write
+ # redirect_to(post) in your controllers;
+ # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
+ # for feed entries.
+ #
+ # == Prefixed polymorphic helpers
+ #
+ # In addition to polymorphic_url and polymorphic_path methods, a
+ # number of prefixed helpers are available as a shorthand to :action => "..."
+ # in options. Those are:
+ #
+ # * edit_polymorphic_url, edit_polymorphic_path
+ # * new_polymorphic_url, new_polymorphic_path
+ #
+ # Example usage:
+ #
+ # edit_polymorphic_path(@post) # => "/posts/1/edit"
+ # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
+ module PolymorphicRoutes
+ # Constructs a call to a named RESTful route for the given record and returns the
+ # resulting URL string. For example:
+ #
+ # # calls post_url(post)
+ # polymorphic_url(post) # => "http://example.com/posts/1"
+ # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
+ # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
+ # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
+ # polymorphic_url(Comment) # => "http://example.com/comments"
+ #
+ # ==== Options
+ #
+ # * :action - Specifies the action prefix for the named route:
+ # :new or :edit. Default is no prefix.
+ # * :routing_type - Allowed values are :path or :url.
+ # Default is :url.
+ #
+ # ==== Examples
+ #
+ # # an Article record
+ # polymorphic_url(record) # same as article_url(record)
+ #
+ # # a Comment record
+ # polymorphic_url(record) # same as comment_url(record)
+ #
+ # # it recognizes new records and maps to the collection
+ # record = Comment.new
+ # polymorphic_url(record) # same as comments_url()
+ #
+ # # the class of a record will also map to the collection
+ # polymorphic_url(Comment) # same as comments_url()
+ #
+ def polymorphic_url(record_or_hash_or_array, options = {})
+ if record_or_hash_or_array.kind_of?(Array)
+ record_or_hash_or_array = record_or_hash_or_array.compact
+ record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
+ end
+
+ record = extract_record(record_or_hash_or_array)
+ record = record.to_model if record.respond_to?(:to_model)
+ namespace = extract_namespace(record_or_hash_or_array)
+
+ args = case record_or_hash_or_array
+ when Hash; [ record_or_hash_or_array ]
+ when Array; record_or_hash_or_array.dup
+ else [ record_or_hash_or_array ]
+ end
+
+ inflection = if options[:action].to_s == "new"
+ args.pop
+ :singular
+ elsif (record.respond_to?(:new_record?) && record.new_record?) ||
+ (record.respond_to?(:destroyed?) && record.destroyed?)
+ args.pop
+ :plural
+ elsif record.is_a?(Class)
+ args.pop
+ :plural
+ else
+ :singular
+ end
+
+ args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
+
+ named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
+
+ url_options = options.except(:action, :routing_type)
+ unless url_options.empty?
+ args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
+ end
+
+ __send__(named_route, *args)
+ end
+
+ # Returns the path component of a URL for the given record. It uses
+ # polymorphic_url with :routing_type => :path.
+ def polymorphic_path(record_or_hash_or_array, options = {})
+ polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
+ end
+
+ %w(edit new).each do |action|
+ module_eval <<-EOT, __FILE__, __LINE__
+ def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
+ end # end
+ #
+ def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
+ end # end
+ EOT
+ end
+
+ def formatted_polymorphic_url(record_or_hash, options = {})
+ ActiveSupport::Deprecation.warn("formatted_polymorphic_url has been deprecated. Please pass :format to the polymorphic_url method instead", caller)
+ options[:format] = record_or_hash.pop if Array === record_or_hash
+ polymorphic_url(record_or_hash, options)
+ end
+
+ def formatted_polymorphic_path(record_or_hash, options = {})
+ ActiveSupport::Deprecation.warn("formatted_polymorphic_path has been deprecated. Please pass :format to the polymorphic_path method instead", caller)
+ options[:format] = record_or_hash.pop if record_or_hash === Array
+ polymorphic_url(record_or_hash, options.merge(:routing_type => :path))
+ end
+
+ private
+ def action_prefix(options)
+ options[:action] ? "#{options[:action]}_" : ''
+ end
+
+ def routing_type(options)
+ options[:routing_type] || :url
+ end
+
+ def build_named_route_call(records, namespace, inflection, options = {})
+ unless records.is_a?(Array)
+ record = extract_record(records)
+ route = ''
+ else
+ record = records.pop
+ route = records.inject("") do |string, parent|
+ if parent.is_a?(Symbol) || parent.is_a?(String)
+ string << "#{parent}_"
+ else
+ string << "#{RecordIdentifier.__send__("plural_class_name", parent)}".singularize
+ string << "_"
+ end
+ end
+ end
+
+ if record.is_a?(Symbol) || record.is_a?(String)
+ route << "#{record}_"
+ else
+ route << "#{RecordIdentifier.__send__("plural_class_name", record)}"
+ route = route.singularize if inflection == :singular
+ route << "_"
+ end
+
+ action_prefix(options) + namespace + route + routing_type(options).to_s
+ end
+
+ def extract_record(record_or_hash_or_array)
+ case record_or_hash_or_array
+ when Array; record_or_hash_or_array.last
+ when Hash; record_or_hash_or_array[:id]
+ else record_or_hash_or_array
+ end
+ end
+
+ # Remove the first symbols from the array and return the url prefix
+ # implied by those symbols.
+ def extract_namespace(record_or_hash_or_array)
+ return "" unless record_or_hash_or_array.is_a?(Array)
+
+ namespace_keys = []
+ while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol)
+ namespace_keys << record_or_hash_or_array.shift
+ end
+
+ namespace_keys.map {|k| "#{k}_"}.join
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb
deleted file mode 100644
index 9bfebff0c0..0000000000
--- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb
+++ /dev/null
@@ -1,167 +0,0 @@
-module ActionController
- module Routing
- # BEFORE: 0.191446860631307 ms/url
- # AFTER: 0.029847304022858 ms/url
- # Speed up: 6.4 times
- #
- # Route recognition is slow due to one-by-one iterating over
- # a whole routeset (each map.resources generates at least 14 routes)
- # and matching weird regexps on each step.
- #
- # We optimize this by skipping all URI segments that 100% sure can't
- # be matched, moving deeper in a tree of routes (where node == segment)
- # until first possible match is accured. In such case, we start walking
- # a flat list of routes, matching them with accurate matcher.
- # So, first step: search a segment tree for the first relevant index.
- # Second step: iterate routes starting with that index.
- #
- # How tree is walked? We can do a recursive tests, but it's smarter:
- # We just create a tree of if-s and elsif-s matching segments.
- #
- # We have segments of 3 flavors:
- # 1) nil (no segment, route finished)
- # 2) const-dot-dynamic (like "/posts.:xml", "/preview.:size.jpg")
- # 3) const (like "/posts", "/comments")
- # 4) dynamic ("/:id", "file.:size.:extension")
- #
- # We split incoming string into segments and iterate over them.
- # When segment is nil, we drop immediately, on a current node index.
- # When segment is equal to some const, we step into branch.
- # If none constants matched, we step into 'dynamic' branch (it's a last).
- # If we can't match anything, we drop to last index on a level.
- #
- # Note: we maintain the original routes order, so we finish building
- # steps on a first dynamic segment.
- #
- #
- # Example. Given the routes:
- # 0 /posts/
- # 1 /posts/:id
- # 2 /posts/:id/comments
- # 3 /posts/blah
- # 4 /users/
- # 5 /users/:id
- # 6 /users/:id/profile
- #
- # request_uri = /users/123
- #
- # There will be only 4 iterations:
- # 1) segm test for /posts prefix, skip all /posts/* routes
- # 2) segm test for /users/
- # 3) segm test for /users/:id
- # (jump to list index = 5)
- # 4) full test for /users/:id => here we are!
- class RouteSet
- def recognize_path(path, environment={})
- result = recognize_optimized(path, environment) and return result
-
- # Route was not recognized. Try to find out why (maybe wrong verb).
- allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, environment.merge(:method => verb)) } }
-
- if environment[:method] && !HTTP_METHODS.include?(environment[:method])
- raise NotImplemented.new(*allows)
- elsif !allows.empty?
- raise MethodNotAllowed.new(*allows)
- else
- raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
- end
- end
-
- def segment_tree(routes)
- tree = [0]
-
- i = -1
- routes.each do |route|
- i += 1
- # not fast, but runs only once
- segments = to_plain_segments(route.segments.inject("") { |str,s| str << s.to_s })
-
- node = tree
- segments.each do |seg|
- seg = :dynamic if seg && seg[0] == ?:
- node << [seg, [i]] if node.empty? || node[node.size - 1][0] != seg
- node = node[node.size - 1][1]
- end
- end
- tree
- end
-
- def generate_code(list, padding=' ', level = 0)
- # a digit
- return padding + "#{list[0]}\n" if list.size == 1 && !(Array === list[0])
-
- body = padding + "(seg = segments[#{level}]; \n"
-
- i = 0
- was_nil = false
- list.each do |item|
- if Array === item
- i += 1
- start = (i == 1)
- tag, sub = item
- if tag == :dynamic
- body += padding + "#{start ? 'if' : 'elsif'} true\n"
- body += generate_code(sub, padding + " ", level + 1)
- break
- elsif tag == nil && !was_nil
- was_nil = true
- body += padding + "#{start ? 'if' : 'elsif'} seg.nil?\n"
- body += generate_code(sub, padding + " ", level + 1)
- else
- body += padding + "#{start ? 'if' : 'elsif'} seg == '#{tag}'\n"
- body += generate_code(sub, padding + " ", level + 1)
- end
- end
- end
- body += padding + "else\n"
- body += padding + " #{list[0]}\n"
- body += padding + "end)\n"
- body
- end
-
- # this must be really fast
- def to_plain_segments(str)
- str = str.dup
- str.sub!(/^\/+/,'')
- str.sub!(/\/+$/,'')
- segments = str.split(/\.[^\/]+\/+|\/+|\.[^\/]+\Z/) # cut off ".format" also
- segments << nil
- segments
- end
-
- private
- def write_recognize_optimized!
- tree = segment_tree(routes)
- body = generate_code(tree)
-
- remove_recognize_optimized!
-
- instance_eval %{
- def recognize_optimized(path, env)
- segments = to_plain_segments(path)
- index = #{body}
- return nil unless index
- while index < routes.size
- result = routes[index].recognize(path, env) and return result
- index += 1
- end
- nil
- end
- }, '(recognize_optimized)', 1
- end
-
- def clear_recognize_optimized!
- remove_recognize_optimized!
- write_recognize_optimized!
- end
-
- def remove_recognize_optimized!
- if respond_to?(:recognize_optimized)
- class << self
- remove_method :recognize_optimized
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb
deleted file mode 100644
index eba05a3c5a..0000000000
--- a/actionpack/lib/action_controller/routing/route.rb
+++ /dev/null
@@ -1,267 +0,0 @@
-require 'active_support/core_ext/object/misc'
-
-module ActionController
- module Routing
- class Route #:nodoc:
- attr_accessor :segments, :requirements, :conditions, :optimise
-
- def initialize(segments = [], requirements = {}, conditions = {})
- @segments = segments
- @requirements = requirements
- @conditions = conditions
-
- if !significant_keys.include?(:action) && !requirements[:action]
- @requirements[:action] = "index"
- @significant_keys << :action
- end
-
- # Routes cannot use the current string interpolation method
- # if there are user-supplied :requirements as the interpolation
- # code won't raise RoutingErrors when generating
- has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp }
- if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
- @optimise = false
- else
- @optimise = true
- end
- end
-
- # Indicates whether the routes should be optimised with the string interpolation
- # version of the named routes methods.
- def optimise?
- @optimise && ActionController::Base::optimise_named_routes
- end
-
- def segment_keys
- segments.collect do |segment|
- segment.key if segment.respond_to? :key
- end.compact
- end
-
- def required_segment_keys
- required_segments = segments.select {|seg| (!seg.optional? && !seg.is_a?(DividerSegment)) || seg.is_a?(PathSegment) }
- required_segments.collect { |seg| seg.key if seg.respond_to?(:key)}.compact
- end
-
- # Build a query string from the keys of the given hash. If +only_keys+
- # is given (as an array), only the keys indicated will be used to build
- # the query string. The query string will correctly build array parameter
- # values.
- def build_query_string(hash, only_keys = nil)
- elements = []
-
- (only_keys || hash.keys).each do |key|
- if value = hash[key]
- elements << value.to_query(key)
- end
- end
-
- elements.empty? ? '' : "?#{elements.sort * '&'}"
- end
-
- # A route's parameter shell contains parameter values that are not in the
- # route's path, but should be placed in the recognized hash.
- #
- # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
- #
- # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
- #
- def parameter_shell
- @parameter_shell ||= {}.tap do |shell|
- requirements.each do |key, requirement|
- shell[key] = requirement unless requirement.is_a? Regexp
- end
- end
- end
-
- # Return an array containing all the keys that are used in this route. This
- # includes keys that appear inside the path, and keys that have requirements
- # placed upon them.
- def significant_keys
- @significant_keys ||= [].tap do |sk|
- segments.each { |segment| sk << segment.key if segment.respond_to? :key }
- sk.concat requirements.keys
- sk.uniq!
- end
- end
-
- # Return a hash of key/value pairs representing the keys in the route that
- # have defaults, or which are specified by non-regexp requirements.
- def defaults
- @defaults ||= {}.tap do |hash|
- segments.each do |segment|
- next unless segment.respond_to? :default
- hash[segment.key] = segment.default unless segment.default.nil?
- end
- requirements.each do |key,req|
- next if Regexp === req || req.nil?
- hash[key] = req
- end
- end
- end
-
- def matches_controller_and_action?(controller, action)
- prepare_matching!
- (@controller_requirement.nil? || @controller_requirement === controller) &&
- (@action_requirement.nil? || @action_requirement === action)
- end
-
- def to_s
- @to_s ||= begin
- segs = segments.inject("") { |str,s| str << s.to_s }
- "%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
- end
- end
-
- # TODO: Route should be prepared and frozen on initialize
- def freeze
- unless frozen?
- write_generation!
- write_recognition!
- prepare_matching!
-
- parameter_shell
- significant_keys
- defaults
- to_s
- end
-
- super
- end
-
- def generate(options, hash, expire_on = {})
- path, hash = generate_raw(options, hash, expire_on)
- append_query_string(path, hash, extra_keys(options))
- end
-
- def generate_extras(options, hash, expire_on = {})
- path, hash = generate_raw(options, hash, expire_on)
- [path, extra_keys(options)]
- end
-
- private
- def requirement_for(key)
- return requirements[key] if requirements.key? key
- segments.each do |segment|
- return segment.regexp if segment.respond_to?(:key) && segment.key == key
- end
- nil
- end
-
- # Write and compile a +generate+ method for this Route.
- def write_generation!
- # Build the main body of the generation
- body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
-
- # If we have conditions that must be tested first, nest the body inside an if
- body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
- args = "options, hash, expire_on = {}"
-
- # Nest the body inside of a def block, and then compile it.
- raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
- instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
-
- # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
- # are the same as the keys that were recalled from the previous request. Thus,
- # we can use the expire_on.keys to determine which keys ought to be used to build
- # the query string. (Never use keys from the recalled request when building the
- # query string.)
-
- raw_method
- end
-
- # Build several lines of code that extract values from the options hash. If any
- # of the values are missing or rejected then a return will be executed.
- def generation_extraction
- segments.collect do |segment|
- segment.extraction_code
- end.compact * "\n"
- end
-
- # Produce a condition expression that will check the requirements of this route
- # upon generation.
- def generation_requirements
- requirement_conditions = requirements.collect do |key, req|
- if req.is_a? Regexp
- value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
- "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
- else
- "hash[:#{key}] == #{req.inspect}"
- end
- end
- requirement_conditions * ' && ' unless requirement_conditions.empty?
- end
-
- def generation_structure
- segments.last.string_structure segments[0..-2]
- end
-
- # Write and compile a +recognize+ method for this Route.
- def write_recognition!
- # Create an if structure to extract the params from a match if it occurs.
- body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
- body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
-
- # Build the method declaration and compile it
- method_decl = "def recognize(path, env = {})\n#{body}\nend"
- instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
- method_decl
- end
-
- # Plugins may override this method to add other conditions, like checks on
- # host, subdomain, and so forth. Note that changes here only affect route
- # recognition, not generation.
- def recognition_conditions
- result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
- result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method]
- result
- end
-
- # Build the regular expression pattern that will match this route.
- def recognition_pattern(wrap = true)
- pattern = ''
- segments.reverse_each do |segment|
- pattern = segment.build_pattern pattern
- end
- wrap ? ("\\A" + pattern + "\\Z") : pattern
- end
-
- # Write the code to extract the parameters from a matched route.
- def recognition_extraction
- next_capture = 1
- extraction = segments.collect do |segment|
- x = segment.match_extraction(next_capture)
- next_capture += segment.number_of_captures
- x
- end
- extraction.compact
- end
-
- # Generate the query string with any extra keys in the hash and append
- # it to the given path, returning the new path.
- def append_query_string(path, hash, query_keys = nil)
- return nil unless path
- query_keys ||= extra_keys(hash)
- "#{path}#{build_query_string(hash, query_keys)}"
- end
-
- # Determine which keys in the given hash are "extra". Extra keys are
- # those that were not used to generate a particular route. The extra
- # keys also do not include those recalled from the prior request, nor
- # do they include any keys that were implied in the route (like a
- # :controller that is required, but not explicitly used in the
- # text of the route.)
- def extra_keys(hash, recall = {})
- (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
- end
-
- def prepare_matching!
- unless defined? @matching_prepared
- @controller_requirement = requirement_for(:controller)
- @action_requirement = requirement_for(:action)
- @matching_prepared = true
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
index 25fdbf480e..8135b5811e 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_controller/routing/route_set.rb
@@ -1,6 +1,58 @@
+require 'rack/mount'
+require 'forwardable'
+
module ActionController
module Routing
class RouteSet #:nodoc:
+ NotFound = lambda { |env|
+ raise RoutingError, "No route matches #{env[::Rack::Mount::Const::PATH_INFO].inspect} with #{env.inspect}"
+ }
+
+ PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
+
+ class Dispatcher
+ def initialize(options = {})
+ defaults = options[:defaults]
+ @glob_param = options.delete(:glob)
+ end
+
+ def call(env)
+ params = env[PARAMETERS_KEY]
+ merge_default_action!(params)
+ split_glob_param!(params) if @glob_param
+ params.each { |key, value| params[key] = URI.unescape(value) if value.is_a?(String) }
+
+ if env['action_controller.recognize']
+ [200, {}, params]
+ else
+ controller = controller(params)
+ controller.action(params[:action]).call(env)
+ end
+ end
+
+ private
+ def controller(params)
+ if params && params.has_key?(:controller)
+ controller = "#{params[:controller].camelize}Controller"
+ ActiveSupport::Inflector.constantize(controller)
+ end
+ end
+
+ def merge_default_action!(params)
+ params[:action] ||= 'index'
+ end
+
+ def split_glob_param!(params)
+ params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) }
+ end
+ end
+
+ module RouteExtensions
+ def segment_keys
+ conditions[:path_info].names.compact.map { |key| key.to_sym }
+ end
+ end
+
# Mapper instances are used to build routes. The object passed to the draw
# block in config/routes.rb is a Mapper instance.
#
@@ -63,7 +115,6 @@ module ActionController
# named routes.
class NamedRouteCollection #:nodoc:
include Enumerable
- include ActionController::Routing::Optimisation
attr_reader :routes, :helpers
def initialize
@@ -175,8 +226,6 @@ module ActionController
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
def #{selector}(*args) # def users_url(*args)
#
- #{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)}
- #
opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
args.first || {} # args.first || {}
else # else
@@ -216,28 +265,18 @@ module ActionController
clear!
end
- # Subclasses and plugins may override this method to specify a different
- # RouteBuilder instance, so that other route DSL's can be created.
- def builder
- @builder ||= RouteBuilder.new
- end
-
def draw
clear!
yield Mapper.new(self)
+ @set.add_route(NotFound)
install_helpers
+ @set.freeze
end
def clear!
routes.clear
named_routes.clear
-
- @combined_regexp = nil
- @routes_by_controller = nil
-
- # This will force routing/recognition_optimization.rb
- # to refresh optimisations.
- clear_recognize_optimized!
+ @set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY)
end
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
@@ -257,7 +296,7 @@ module ActionController
def configuration_file=(path)
add_configuration_file(path)
end
-
+
# Deprecated accessor
def configuration_file
configuration_files
@@ -296,29 +335,119 @@ module ActionController
def routes_changed_at
routes_changed_at = nil
-
+
configuration_files.each do |config|
config_changed_at = File.stat(config).mtime
if routes_changed_at.nil? || config_changed_at > routes_changed_at
- routes_changed_at = config_changed_at
+ routes_changed_at = config_changed_at
end
end
-
+
routes_changed_at
end
def add_route(path, options = {})
- options.each { |k, v| options[k] = v.to_s if [:controller, :action].include?(k) && v.is_a?(Symbol) }
- route = builder.build(path, options)
+ options = options.dup
+
+ if conditions = options.delete(:conditions)
+ conditions = conditions.dup
+ method = [conditions.delete(:method)].flatten.compact
+ method.map! { |m|
+ m = m.to_s.upcase
+
+ if m == "HEAD"
+ raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
+ end
+
+ unless HTTP_METHODS.include?(m.downcase.to_sym)
+ raise ArgumentError, "Invalid HTTP method specified in route conditions"
+ end
+
+ m
+ }
+
+ if method.length > 1
+ method = Regexp.union(*method)
+ elsif method.length == 1
+ method = method.first
+ else
+ method = nil
+ end
+ end
+
+ path_prefix = options.delete(:path_prefix)
+ name_prefix = options.delete(:name_prefix)
+ namespace = options.delete(:namespace)
+
+ name = options.delete(:_name)
+ name = "#{name_prefix}#{name}" if name_prefix
+
+ requirements = options.delete(:requirements) || {}
+ defaults = options.delete(:defaults) || {}
+ options.each do |k, v|
+ if v.is_a?(Regexp)
+ if value = options.delete(k)
+ requirements[k.to_sym] = value
+ end
+ else
+ value = options.delete(k)
+ defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
+ end
+ end
+
+ requirements.each do |_, requirement|
+ if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
+ end
+ if requirement.multiline?
+ raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
+ end
+ end
+
+ possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
+ requirements[:controller] ||= Regexp.union(*possible_names)
+
+ if defaults[:controller]
+ defaults[:action] ||= 'index'
+ defaults[:controller] = defaults[:controller].to_s
+ defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
+ end
+
+ if defaults[:action]
+ defaults[:action] = defaults[:action].to_s
+ end
+
+ if path.is_a?(String)
+ path = "#{path_prefix}/#{path}" if path_prefix
+ path = path.gsub('.:format', '(.:format)')
+ path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
+ glob = $1.to_sym if path =~ /\/\*(\w+)$/
+ path = ::Rack::Mount::Utils.normalize_path(path)
+ path = ::Rack::Mount::Strexp.compile(path, requirements, %w( / . ? ))
+
+ if glob && !defaults[glob].blank?
+ raise RoutingError, "paths cannot have non-empty default values"
+ end
+ end
+
+ app = Dispatcher.new(:defaults => defaults, :glob => glob)
+
+ conditions = {}
+ conditions[:request_method] = method if method
+ conditions[:path_info] = path if path
+
+ route = @set.add_route(app, conditions, defaults, name)
+ route.extend(RouteExtensions)
routes << route
route
end
def add_named_route(name, path, options = {})
- # TODO - is options EVER used?
- name = options[:name_prefix] + name.to_s if options[:name_prefix]
- named_routes[name.to_sym] = add_route(path, options)
+ options[:_name] = name
+ route = add_route(path, options)
+ named_routes[route.name] = route
+ route
end
def options_as_params(options)
@@ -356,24 +485,29 @@ module ActionController
generate(options, recall, :generate_extras)
end
- def generate(options, recall = {}, method=:generate)
- named_route_name = options.delete(:use_route)
- generate_all = options.delete(:generate_all)
- if named_route_name
- named_route = named_routes[named_route_name]
- options = named_route.parameter_shell.merge(options)
- end
+ def generate(options, recall = {}, method = :generate)
+ options, recall = options.dup, recall.dup
+ named_route = options.delete(:use_route)
options = options_as_params(options)
expire_on = build_expiry(options, recall)
- if options[:controller]
- options[:controller] = options[:controller].to_s
+ recall[:action] ||= 'index' if options[:controller] || recall[:controller]
+
+ if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller])
+ options[:controller] = recall.delete(:controller)
+
+ if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action])
+ options[:action] = recall.delete(:action)
+
+ if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id])
+ options[:id] = recall.delete(:id)
+ end
+ end
end
- # if the controller has changed, make sure it changes relative to the
- # current controller module, if any. In other words, if we're currently
- # on admin/get, and the new controller is 'set', the new controller
- # should really be admin/set.
+
+ options[:controller] = options[:controller].to_s if options[:controller]
+
if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
old_parts = recall[:controller].split('/')
new_parts = options[:controller].split('/')
@@ -381,98 +515,75 @@ module ActionController
options[:controller] = parts.join('/')
end
- # drop the leading '/' on the controller name
options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
- merged = recall.merge(options)
- if named_route
- path = named_route.generate(options, merged, expire_on)
- if path.nil?
- raise_named_route_error(options, named_route, named_route_name)
- else
- return path
- end
+ merged = options.merge(recall)
+ if options.has_key?(:action) && options[:action].nil?
+ options.delete(:action)
+ recall[:action] = 'index'
+ end
+ recall[:action] = options.delete(:action) if options[:action] == 'index'
+
+ path = _uri(named_route, options, recall)
+ if path && method == :generate_extras
+ uri = URI(path)
+ extras = uri.query ?
+ Rack::Utils.parse_nested_query(uri.query).keys.map { |k| k.to_sym } :
+ []
+ [uri.path, extras]
+ elsif path
+ path
else
- merged[:action] ||= 'index'
- options[:action] ||= 'index'
-
- controller = merged[:controller]
- action = merged[:action]
-
- raise RoutingError, "Need controller and action!" unless controller && action
-
- if generate_all
- # Used by caching to expire all paths for a resource
- return routes.collect do |route|
- route.__send__(method, options, merged, expire_on)
- end.compact
- end
-
- # don't use the recalled keys when determining which routes to check
- routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }]
-
- routes.each_with_index do |route, index|
- results = route.__send__(method, options, merged, expire_on)
- if results && (!results.is_a?(Array) || results.first)
- return results
- end
- end
+ raise RoutingError, "No route matches #{options.inspect}"
end
-
+ rescue Rack::Mount::RoutingError
raise RoutingError, "No route matches #{options.inspect}"
end
- # try to give a helpful error message when named route generation fails
- def raise_named_route_error(options, named_route, named_route_name)
- diff = named_route.requirements.diff(options)
- unless diff.empty?
- raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
+ def call(env)
+ @set.call(env)
+ rescue ActionController::RoutingError => e
+ raise e if env['action_controller.rescue_error'] == false
+
+ method, path = env['REQUEST_METHOD'].downcase.to_sym, env['PATH_INFO']
+
+ # Route was not recognized. Try to find out why (maybe wrong verb).
+ allows = HTTP_METHODS.select { |verb|
+ begin
+ recognize_path(path, {:method => verb}, false)
+ rescue ActionController::RoutingError
+ nil
+ end
+ }
+
+ if !HTTP_METHODS.include?(method)
+ raise NotImplemented.new(*allows)
+ elsif !allows.empty?
+ raise MethodNotAllowed.new(*allows)
else
- required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
- required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
- raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?"
+ raise e
end
end
- def call(env)
- request = ActionDispatch::Request.new(env)
- app = Routing::Routes.recognize(request)
- app.action(request.parameters[:action] || 'index').call(env)
- end
-
def recognize(request)
params = recognize_path(request.path, extract_request_environment(request))
request.path_parameters = params.with_indifferent_access
"#{params[:controller].to_s.camelize}Controller".constantize
end
- def recognize_path(path, environment={})
- raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path."
- end
+ def recognize_path(path, environment = {}, rescue_error = true)
+ method = (environment[:method] || "GET").to_s.upcase
- def routes_by_controller
- @routes_by_controller ||= Hash.new do |controller_hash, controller|
- controller_hash[controller] = Hash.new do |action_hash, action|
- action_hash[action] = Hash.new do |key_hash, keys|
- key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
- end
- end
+ begin
+ env = Rack::MockRequest.env_for(path, {:method => method})
+ rescue URI::InvalidURIError => e
+ raise RoutingError, e.message
end
- end
-
- def routes_for(options, merged, expire_on)
- raise "Need controller and action!" unless controller && action
- controller = merged[:controller]
- merged = options if expire_on[:controller]
- action = merged[:action] || 'index'
-
- routes_by_controller[controller][action][merged.keys][1]
- end
- def routes_for_controller_and_action_and_keys(controller, action, keys)
- routes.select do |route|
- route.matches_controller_and_action? controller, action
- end
+ env['action_controller.recognize'] = true
+ env['action_controller.rescue_error'] = rescue_error
+ status, headers, body = call(env)
+ body
end
# Subclasses and plugins may override this method to extract further attributes
@@ -480,6 +591,109 @@ module ActionController
def extract_request_environment(request)
{ :method => request.method }
end
+
+ private
+ def _uri(named_route, params, recall)
+ params = URISegment.wrap_values(params)
+ recall = URISegment.wrap_values(recall)
+
+ unless result = @set.generate(:path_info, named_route, params, recall)
+ return
+ end
+
+ uri, params = result
+ params.each do |k, v|
+ if v._value
+ params[k] = v._value
+ else
+ params.delete(k)
+ end
+ end
+
+ uri << "?#{Rack::Mount::Utils.build_nested_query(params)}" if uri && params.any?
+ uri
+ end
+
+ class URISegment < Struct.new(:_value, :_escape)
+ EXCLUDED = [:controller]
+
+ def self.wrap_values(hash)
+ hash.inject({}) { |h, (k, v)|
+ h[k] = new(v, !EXCLUDED.include?(k.to_sym))
+ h
+ }
+ end
+
+ extend Forwardable
+ def_delegators :_value, :==, :eql?, :hash
+
+ def to_param
+ @to_param ||= begin
+ if _value.is_a?(Array)
+ _value.map { |v| _escaped(v) }.join('/')
+ else
+ _escaped(_value)
+ end
+ end
+ end
+ alias_method :to_s, :to_param
+
+ private
+ def _escaped(value)
+ v = value.respond_to?(:to_param) ? value.to_param : value
+ _escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s
+ end
+ end
+
+ def optionalize_trailing_dynamic_segments(path, requirements, defaults)
+ path = (path =~ /^\//) ? path.dup : "/#{path}"
+ optional, segments = true, []
+
+ required_segments = requirements.keys
+ required_segments -= defaults.keys.compact
+
+ old_segments = path.split('/')
+ old_segments.shift
+ length = old_segments.length
+
+ old_segments.reverse.each_with_index do |segment, index|
+ required_segments.each do |required|
+ if segment =~ /#{required}/
+ optional = false
+ break
+ end
+ end
+
+ if optional
+ if segment == ":id" && segments.include?(":action")
+ optional = false
+ elsif segment == ":controller" || segment == ":action" || segment == ":id"
+ # Ignore
+ elsif !(segment =~ /^:\w+$/) &&
+ !(segment =~ /^:\w+\(\.:format\)$/)
+ optional = false
+ elsif segment =~ /^:(\w+)$/
+ if defaults.has_key?($1.to_sym)
+ defaults.delete($1.to_sym)
+ else
+ optional = false
+ end
+ end
+ end
+
+ if optional && index < length - 1
+ segments.unshift('(/', segment)
+ segments.push(')')
+ elsif optional
+ segments.unshift('/(', segment)
+ segments.push(')')
+ else
+ segments.unshift('/', segment)
+ end
+ end
+
+ segments.join
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/routing/routing_ext.rb b/actionpack/lib/action_controller/routing/routing_ext.rb
deleted file mode 100644
index 5e5b22b6c2..0000000000
--- a/actionpack/lib/action_controller/routing/routing_ext.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-require 'active_support/core_ext/object/conversions'
-require 'active_support/core_ext/boolean/conversions'
-require 'active_support/core_ext/nil/conversions'
-require 'active_support/core_ext/regexp'
diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb
deleted file mode 100644
index 2603855476..0000000000
--- a/actionpack/lib/action_controller/routing/segments.rb
+++ /dev/null
@@ -1,343 +0,0 @@
-module ActionController
- module Routing
- class Segment #:nodoc:
- RESERVED_PCHAR = ':@&=+$,;%'
- SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
- if RUBY_VERSION >= '1.9'
- UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
- else
- UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
- end
-
- # TODO: Convert :is_optional accessor to read only
- attr_accessor :is_optional
- alias_method :optional?, :is_optional
-
- def initialize
- @is_optional = false
- end
-
- def number_of_captures
- Regexp.new(regexp_chunk).number_of_captures
- end
-
- def extraction_code
- nil
- end
-
- # Continue generating string for the prior segments.
- def continue_string_structure(prior_segments)
- if prior_segments.empty?
- interpolation_statement(prior_segments)
- else
- new_priors = prior_segments[0..-2]
- prior_segments.last.string_structure(new_priors)
- end
- end
-
- def interpolation_chunk
- URI.escape(value, UNSAFE_PCHAR)
- end
-
- # Return a string interpolation statement for this segment and those before it.
- def interpolation_statement(prior_segments)
- chunks = prior_segments.collect { |s| s.interpolation_chunk }
- chunks << interpolation_chunk
- "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
- end
-
- def string_structure(prior_segments)
- optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
- end
-
- # Return an if condition that is true if all the prior segments can be generated.
- # If there are no optional segments before this one, then nil is returned.
- def all_optionals_available_condition(prior_segments)
- optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
- optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
- end
-
- # Recognition
-
- def match_extraction(next_capture)
- nil
- end
-
- # Warning
-
- # Returns true if this segment is optional? because of a default. If so, then
- # no warning will be emitted regarding this segment.
- def optionality_implied?
- false
- end
- end
-
- class StaticSegment < Segment #:nodoc:
- attr_reader :value, :raw
- alias_method :raw?, :raw
-
- def initialize(value = nil, options = {})
- super()
- @value = value
- @raw = options[:raw] if options.key?(:raw)
- @is_optional = options[:optional] if options.key?(:optional)
- end
-
- def interpolation_chunk
- raw? ? value : super
- end
-
- def regexp_chunk
- chunk = Regexp.escape(value)
- optional? ? Regexp.optionalize(chunk) : chunk
- end
-
- def number_of_captures
- 0
- end
-
- def build_pattern(pattern)
- escaped = Regexp.escape(value)
- if optional? && ! pattern.empty?
- "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
- elsif optional?
- Regexp.optionalize escaped
- else
- escaped + pattern
- end
- end
-
- def to_s
- value
- end
- end
-
- class DividerSegment < StaticSegment #:nodoc:
- def initialize(value = nil, options = {})
- super(value, {:raw => true, :optional => true}.merge(options))
- end
-
- def optionality_implied?
- true
- end
- end
-
- class DynamicSegment < Segment #:nodoc:
- attr_reader :key
-
- # TODO: Convert these accessors to read only
- attr_accessor :default, :regexp
-
- def initialize(key = nil, options = {})
- super()
- @key = key
- @default = options[:default] if options.key?(:default)
- @regexp = options[:regexp] if options.key?(:regexp)
- @is_optional = true if options[:optional] || options.key?(:default)
- end
-
- def to_s
- ":#{key}"
- end
-
- # The local variable name that the value of this segment will be extracted to.
- def local_name
- "#{key}_value"
- end
-
- def extract_value
- "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
- end
-
- def value_check
- if default # Then we know it won't be nil
- "#{value_regexp.inspect} =~ #{local_name}" if regexp
- elsif optional?
- # If we have a regexp check that the value is not given, or that it matches.
- # If we have no regexp, return nil since we do not require a condition.
- "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp
- else # Then it must be present, and if we have a regexp, it must match too.
- "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
- end
- end
-
- def expiry_statement
- "expired, hash = true, options if !expired && expire_on[:#{key}]"
- end
-
- def extraction_code
- s = extract_value
- vc = value_check
- s << "\nreturn [nil,nil] unless #{vc}" if vc
- s << "\n#{expiry_statement}"
- end
-
- def interpolation_chunk(value_code = local_name)
- "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
- end
-
- def string_structure(prior_segments)
- if optional? # We have a conditional to do...
- # If we should not appear in the url, just write the code for the prior
- # segments. This occurs if our value is the default value, or, if we are
- # optional, if we have nil as our value.
- "if #{local_name} == #{default.inspect}\n" +
- continue_string_structure(prior_segments) +
- "\nelse\n" + # Otherwise, write the code up to here
- "#{interpolation_statement(prior_segments)}\nend"
- else
- interpolation_statement(prior_segments)
- end
- end
-
- def value_regexp
- Regexp.new "\\A#{regexp.to_s}\\Z" if regexp
- end
-
- def regexp_chunk
- regexp ? regexp_string : default_regexp_chunk
- end
-
- def regexp_string
- regexp_has_modifiers? ? "(#{regexp.to_s})" : "(#{regexp.source})"
- end
-
- def default_regexp_chunk
- "([^#{Routing::SEPARATORS.join}]+)"
- end
-
- def number_of_captures
- regexp ? regexp.number_of_captures + 1 : 1
- end
-
- def build_pattern(pattern)
- pattern = "#{regexp_chunk}#{pattern}"
- optional? ? Regexp.optionalize(pattern) : pattern
- end
-
- def match_extraction(next_capture)
- # All non code-related keys (such as :id, :slug) are URI-unescaped as
- # path parameters.
- default_value = default ? default.inspect : nil
- %[
- value = if (m = match[#{next_capture}])
- URI.unescape(m)
- else
- #{default_value}
- end
- params[:#{key}] = value if value
- ]
- end
-
- def optionality_implied?
- [:action, :id].include? key
- end
-
- def regexp_has_modifiers?
- regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
- end
- end
-
- class ControllerSegment < DynamicSegment #:nodoc:
- def regexp_chunk
- possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
- "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
- end
-
- # Don't URI.escape the controller name since it may contain slashes.
- def interpolation_chunk(value_code = local_name)
- "\#{#{value_code}.to_s}"
- end
-
- # Make sure controller names like Admin/Content are correctly normalized to
- # admin/content
- def extract_value
- "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
- end
-
- def match_extraction(next_capture)
- if default
- "params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'"
- else
- "params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]"
- end
- end
- end
-
- class PathSegment < DynamicSegment #:nodoc:
- def interpolation_chunk(value_code = local_name)
- "\#{#{value_code}}"
- end
-
- def extract_value
- "#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
- end
-
- def default
- ''
- end
-
- def default=(path)
- raise RoutingError, "paths cannot have non-empty default values" unless path.blank?
- end
-
- def match_extraction(next_capture)
- "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
- end
-
- def default_regexp_chunk
- "(.*)"
- end
-
- def number_of_captures
- regexp ? regexp.number_of_captures : 1
- end
-
- def optionality_implied?
- true
- end
-
- class Result < ::Array #:nodoc:
- def to_s() join '/' end
- def self.new_escaped(strings)
- new strings.collect {|str| URI.unescape str}
- end
- end
- end
-
- # The OptionalFormatSegment allows for any resource route to have an optional
- # :format, which decreases the amount of routes created by 50%.
- class OptionalFormatSegment < DynamicSegment
-
- def initialize(key = nil, options = {})
- super(:format, {:optional => true}.merge(options))
- end
-
- def interpolation_chunk
- "." + super
- end
-
- def regexp_chunk
- '/|(\.[^/?\.]+)?'
- end
-
- def to_s
- '(.:format)?'
- end
-
- def extract_value
- "#{local_name} = options[:#{key}] && options[:#{key}].to_s.downcase"
- end
-
- #the value should not include the period (.)
- def match_extraction(next_capture)
- %[
- if (m = match[#{next_capture}])
- params[:#{key}] = URI.unescape(m.from(1))
- end
- ]
- end
- end
-
- end
-end
diff --git a/actionpack/lib/action_controller/routing/url_rewriter.rb b/actionpack/lib/action_controller/routing/url_rewriter.rb
new file mode 100644
index 0000000000..52b66c9303
--- /dev/null
+++ b/actionpack/lib/action_controller/routing/url_rewriter.rb
@@ -0,0 +1,204 @@
+module ActionController
+ # In routes.rb one defines URL-to-controller mappings, but the reverse
+ # is also possible: an URL can be generated from one of your routing definitions.
+ # URL generation functionality is centralized in this module.
+ #
+ # See ActionController::Routing and ActionController::Resources for general
+ # information about routing and routes.rb.
+ #
+ # Tip: If you need to generate URLs from your models or some other place,
+ # then ActionController::UrlWriter is what you're looking for. Read on for
+ # an introduction.
+ #
+ # == URL generation from parameters
+ #
+ # As you may know, some functions - such as ActionController::Base#url_for
+ # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
+ # of parameters. For example, you've probably had the chance to write code
+ # like this in one of your views:
+ #
+ # <%= link_to('Click here', :controller => 'users',
+ # :action => 'new', :message => 'Welcome!') %>
+ #
+ # #=> Generates a link to: /users/new?message=Welcome%21
+ #
+ # link_to, and all other functions that require URL generation functionality,
+ # actually use ActionController::UrlWriter under the hood. And in particular,
+ # they use the ActionController::UrlWriter#url_for method. One can generate
+ # the same path as the above example by using the following code:
+ #
+ # include UrlWriter
+ # url_for(:controller => 'users',
+ # :action => 'new',
+ # :message => 'Welcome!',
+ # :only_path => true)
+ # # => "/users/new?message=Welcome%21"
+ #
+ # Notice the :only_path => true part. This is because UrlWriter has no
+ # information about the website hostname that your Rails app is serving. So if you
+ # want to include the hostname as well, then you must also pass the :host
+ # argument:
+ #
+ # include UrlWriter
+ # url_for(:controller => 'users',
+ # :action => 'new',
+ # :message => 'Welcome!',
+ # :host => 'www.example.com') # Changed this.
+ # # => "http://www.example.com/users/new?message=Welcome%21"
+ #
+ # By default, all controllers and views have access to a special version of url_for,
+ # that already knows what the current hostname is. So if you use url_for in your
+ # controllers or your views, then you don't need to explicitly pass the :host
+ # argument.
+ #
+ # For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
+ # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
+ # in full. However, mailers don't have hostname information, and what's why you'll still
+ # have to specify the :host argument when generating URLs in mailers.
+ #
+ #
+ # == URL generation for named routes
+ #
+ # UrlWriter also allows one to access methods that have been auto-generated from
+ # named routes. For example, suppose that you have a 'users' resource in your
+ # routes.rb:
+ #
+ # map.resources :users
+ #
+ # This generates, among other things, the method users_path. By default,
+ # this method is accessible from your controllers, views and mailers. If you need
+ # to access this auto-generated method from other places (such as a model), then
+ # you can do that by including ActionController::UrlWriter in your class:
+ #
+ # class User < ActiveRecord::Base
+ # include ActionController::UrlWriter
+ #
+ # def base_uri
+ # user_path(self)
+ # end
+ # end
+ #
+ # User.find(1).base_uri # => "/users/1"
+ module UrlWriter
+ def self.included(base) #:nodoc:
+ ActionController::Routing::Routes.install_helpers(base)
+ base.mattr_accessor :default_url_options
+
+ # The default options for urls written by this writer. Typically a :host pair is provided.
+ base.default_url_options ||= {}
+ end
+
+ # Generate a url based on the options provided, default_url_options and the
+ # routes defined in routes.rb. The following options are supported:
+ #
+ # * :only_path - If true, the relative url is returned. Defaults to +false+.
+ # * :protocol - The protocol to connect to. Defaults to 'http'.
+ # * :host - Specifies the host the link should be targeted at.
+ # If :only_path is false, this option must be
+ # provided either explicitly, or via +default_url_options+.
+ # * :port - Optionally specify the port to connect to.
+ # * :anchor - An anchor name to be appended to the path.
+ # * :skip_relative_url_root - If true, the url is not constructed using the
+ # +relative_url_root+ set in ActionController::Base.relative_url_root.
+ # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2009/"
+ #
+ # Any other key (:controller, :action, etc.) given to
+ # +url_for+ is forwarded to the Routes module.
+ #
+ # Examples:
+ #
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
+ # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
+ def url_for(options)
+ options = self.class.default_url_options.merge(options)
+
+ url = ''
+
+ unless options.delete(:only_path)
+ url << (options.delete(:protocol) || 'http')
+ url << '://' unless url.match("://")
+
+ raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
+
+ url << options.delete(:host)
+ url << ":#{options.delete(:port)}" if options.key?(:port)
+ else
+ # Delete the unused options to prevent their appearance in the query string.
+ [:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
+ end
+ trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
+ url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
+ anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
+ generated = Routing::Routes.generate(options, {})
+ url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
+ url << anchor if anchor
+
+ url
+ end
+ end
+
+ # Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
+ class UrlRewriter #:nodoc:
+ RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
+ def initialize(request, parameters)
+ @request, @parameters = request, parameters
+ end
+
+ def rewrite(options = {})
+ rewrite_url(options)
+ end
+
+ def to_str
+ "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}"
+ end
+
+ alias_method :to_s, :to_str
+
+ private
+ # Given a path and options, returns a rewritten URL string
+ def rewrite_url(options)
+ rewritten_url = ""
+
+ unless options[:only_path]
+ rewritten_url << (options[:protocol] || @request.protocol)
+ rewritten_url << "://" unless rewritten_url.match("://")
+ rewritten_url << rewrite_authentication(options)
+ rewritten_url << (options[:host] || @request.host_with_port)
+ rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
+ end
+
+ path = rewrite_path(options)
+ rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
+ rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ rewritten_url << "##{CGI.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
+
+ rewritten_url
+ end
+
+ # Given a Hash of options, generates a route
+ def rewrite_path(options)
+ options = options.symbolize_keys
+ options.update(options[:params].symbolize_keys) if options[:params]
+
+ if (overwrite = options.delete(:overwrite_params))
+ options.update(@parameters.symbolize_keys)
+ options.update(overwrite.symbolize_keys)
+ end
+
+ RESERVED_OPTIONS.each { |k| options.delete(k) }
+
+ # Generates the query string, too
+ Routing::Routes.generate(options, @request.symbolized_path_parameters)
+ end
+
+ def rewrite_authentication(options)
+ if options[:user] && options[:password]
+ "#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@"
+ else
+ ""
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/testing/test_case.rb b/actionpack/lib/action_controller/testing/test_case.rb
index 178e3477a6..01a55fe930 100644
--- a/actionpack/lib/action_controller/testing/test_case.rb
+++ b/actionpack/lib/action_controller/testing/test_case.rb
@@ -10,6 +10,13 @@ module ActionController
self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16))
end
+ class Result < ::Array #:nodoc:
+ def to_s() join '/' end
+ def self.new_escaped(strings)
+ new strings.collect {|str| URI.unescape str}
+ end
+ end
+
def assign_parameters(controller_path, action, parameters = {})
parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
@@ -18,7 +25,7 @@ module ActionController
if value.is_a? Fixnum
value = value.to_s
elsif value.is_a? Array
- value = ActionController::Routing::PathSegment::Result.new(value)
+ value = Result.new(value)
end
if extra_keys.include?(key.to_sym)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 67448e66b9..cbfc8267f2 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -11,14 +11,6 @@ end
ROUTING = ActionController::Routing
-class ROUTING::RouteBuilder
- attr_reader :warn_output
-
- def warn(msg)
- (@warn_output ||= []) << msg
- end
-end
-
# See RFC 3986, section 3.3 for allowed path characters.
class UriReservedCharactersRoutingTest < Test::Unit::TestCase
def setup
@@ -1626,25 +1618,6 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal '/pages/show/hello+world', default_route_set.generate(expected, expected)
end
- def test_parameter_shell
- page_url = ROUTING::Route.new
- page_url.requirements = {:controller => 'pages', :action => 'show', :id => /\d+/}
- assert_equal({:controller => 'pages', :action => 'show'}, page_url.parameter_shell)
- end
-
- def test_defaults
- route = ROUTING::RouteBuilder.new.build '/users/:id.:format', :controller => "users", :action => "show", :format => "html"
- assert_equal(
- { :controller => "users", :action => "show", :format => "html" },
- route.defaults)
- end
-
- def test_builder_complains_without_controller
- assert_raise(ArgumentError) do
- ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index"
- end
- end
-
def test_build_empty_query_string
assert_uri_equal '/foo', default_route_set.generate({:controller => 'foo'})
end
diff --git a/activesupport/lib/active_support/core_ext/regexp.rb b/activesupport/lib/active_support/core_ext/regexp.rb
index 95d06ee6ee..784145f5fb 100644
--- a/activesupport/lib/active_support/core_ext/regexp.rb
+++ b/activesupport/lib/active_support/core_ext/regexp.rb
@@ -1,27 +1,5 @@
class Regexp #:nodoc:
- def number_of_captures
- Regexp.new("|#{source}").match('').captures.length
- end
-
def multiline?
options & MULTILINE == MULTILINE
end
-
- class << self
- def optionalize(pattern)
- return pattern if pattern == ""
-
- case unoptionalize(pattern)
- when /\A(.|\(.*\))\Z/ then "#{pattern}?"
- else "(?:#{pattern})?"
- end
- end
-
- def unoptionalize(pattern)
- [/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp|
- return $1 if regexp =~ pattern
- end
- return pattern
- end
- end
end
diff --git a/activesupport/test/core_ext/regexp_ext_test.rb b/activesupport/test/core_ext/regexp_ext_test.rb
index cc3f07d5c5..68b089d5b4 100644
--- a/activesupport/test/core_ext/regexp_ext_test.rb
+++ b/activesupport/test/core_ext/regexp_ext_test.rb
@@ -2,28 +2,9 @@ require 'abstract_unit'
require 'active_support/core_ext/regexp'
class RegexpExtAccessTests < Test::Unit::TestCase
- def test_number_of_captures
- assert_equal 0, //.number_of_captures
- assert_equal 1, /.(.)./.number_of_captures
- assert_equal 2, /.(.).(?:.).(.)/.number_of_captures
- assert_equal 3, /.((.).(?:.).(.))/.number_of_captures
- end
-
def test_multiline
assert_equal true, //m.multiline?
assert_equal false, //.multiline?
assert_equal false, /(?m:)/.multiline?
end
-
- def test_optionalize
- assert_equal "a?", Regexp.optionalize("a")
- assert_equal "(?:foo)?", Regexp.optionalize("foo")
- assert_equal "", Regexp.optionalize("")
- end
-
- def test_unoptionalize
- assert_equal "a", Regexp.unoptionalize("a?")
- assert_equal "foo", Regexp.unoptionalize("(?:foo)?")
- assert_equal "", Regexp.unoptionalize("")
- end
end
--
cgit v1.2.3
From e994bf0c810925ccdbe08cff1774b2c0865e491d Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 20 Oct 2009 09:27:06 -0500
Subject: Add rackmount to actionmailer gemspec
---
actionmailer/Gemfile | 1 +
1 file changed, 1 insertion(+)
diff --git a/actionmailer/Gemfile b/actionmailer/Gemfile
index 5dec362d57..430b32e8ca 100644
--- a/actionmailer/Gemfile
+++ b/actionmailer/Gemfile
@@ -4,6 +4,7 @@ sibling = "#{File.dirname(__FILE__)}/.."
gem "activesupport", "3.0.pre", :vendored_at => "#{sibling}/activesupport"
gem "activemodel", "3.0.pre", :vendored_at => "#{sibling}/activemodel"
gem "actionpack", "3.0.pre", :vendored_at => "#{sibling}/actionpack"
+gem "rack-mount", :git => "git://github.com/josh/rack-mount.git"
gem "mocha"
#disable_system_gems
--
cgit v1.2.3
From 80a3983b7b987ef11c8875c5c23707b4903cdf72 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 20 Oct 2009 09:27:37 -0500
Subject: Add rackmount to railties gemspec
---
railties/Gemfile | 1 +
1 file changed, 1 insertion(+)
diff --git a/railties/Gemfile b/railties/Gemfile
index f7e7b3ffc0..c441d69ded 100644
--- a/railties/Gemfile
+++ b/railties/Gemfile
@@ -5,6 +5,7 @@ gem "mocha"
gem "arel", :git => "git://github.com/rails/arel.git"
gem "rack", "1.0.1", :git => "git://github.com/rails/rack.git", :branch => "rack-1.0"
+gem "rack-mount", :git => "git://github.com/josh/rack-mount.git"
gem "rack-test", "~> 0.5.0"
%w(activesupport activemodel actionpack actionmailer activerecord activeresource).each do |lib|
--
cgit v1.2.3
From fa1926ddaa7ad481c55b76d1f2c1952721b7b586 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 20 Oct 2009 09:32:26 -0500
Subject: Exclude gem backtrace filter if rubygems is not loaded
---
railties/lib/rails/backtrace_cleaner.rb | 4 ++--
railties/test/backtrace_cleaner_test.rb | 33 +++++++++++++++++----------------
2 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb
index 2f5632c3e4..cd7dd0f80a 100644
--- a/railties/lib/rails/backtrace_cleaner.rb
+++ b/railties/lib/rails/backtrace_cleaner.rb
@@ -27,10 +27,10 @@ module Rails
add_silencer { |line| RAILS_GEMS.any? { |gem| line =~ /^#{gem} / } }
add_silencer { |line| line =~ %r(vendor/plugins/[^\/]+/lib) }
end
-
-
+
private
def add_gem_filters
+ return unless defined? Gem
(Gem.path + [Gem.default_dir]).uniq.each do |path|
# http://gist.github.com/30430
add_filter { |line| line.sub(/(#{path})\/gems\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) \4')}
diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb
index 64a47712b7..4e273852e0 100644
--- a/railties/test/backtrace_cleaner_test.rb
+++ b/railties/test/backtrace_cleaner_test.rb
@@ -29,25 +29,26 @@ else
$stderr.puts 'No BacktraceFilter for minitest'
end
-class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase
- def setup
- @cleaner = Rails::BacktraceCleaner.new
- end
-
- test "should format installed gems correctly" do
- @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
- @result = @cleaner.clean(@backtrace)
- assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
- end
+if defined? Gem
+ class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase
+ def setup
+ @cleaner = Rails::BacktraceCleaner.new
+ end
- test "should format installed gems not in Gem.default_dir correctly" do
- @target_dir = Gem.path.detect { |p| p != Gem.default_dir }
- # skip this test if default_dir is the only directory on Gem.path
- if @target_dir
- @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
+ test "should format installed gems correctly" do
+ @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
@result = @cleaner.clean(@backtrace)
assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
end
- end
+ test "should format installed gems not in Gem.default_dir correctly" do
+ @target_dir = Gem.path.detect { |p| p != Gem.default_dir }
+ # skip this test if default_dir is the only directory on Gem.path
+ if @target_dir
+ @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
+ @result = @cleaner.clean(@backtrace)
+ assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
+ end
+ end
+ end
end
--
cgit v1.2.3
From 3895e2ccb3fe7cfcf36794fe9c941dc1381a0cb7 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 20 Oct 2009 09:52:59 -0500
Subject: Donate tests I wrote for rackmount rails integration
---
actionpack/test/controller/routing_test.rb | 314 ++++++++++++++++++++-
actionpack/test/lib/controller/fake_controllers.rb | 5 +-
2 files changed, 316 insertions(+), 3 deletions(-)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index cbfc8267f2..308e2a85b1 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -110,8 +110,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
ActionController::Base.optimise_named_routes = true
@rs = ::ActionController::Routing::RouteSet.new
-
- ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed)
end
def teardown
@@ -1736,3 +1734,315 @@ class RouteLoadingTest < Test::Unit::TestCase
ActionController::Routing::Routes
end
end
+
+class RackMountIntegrationTests < ActiveSupport::TestCase
+ Model = Struct.new(:to_param)
+
+ Mapping = lambda { |map|
+ map.namespace :admin do |admin|
+ admin.resources :users
+ end
+
+ map.namespace 'api' do |api|
+ api.root :controller => 'users'
+ end
+
+ map.connect 'blog/:year/:month/:day',
+ :controller => 'posts',
+ :action => 'show_date',
+ :requirements => { :year => /(19|20)\d\d/, :month => /[01]?\d/, :day => /[0-3]?\d/},
+ :day => nil,
+ :month => nil
+
+ map.blog('archive/:year', :controller => 'archive', :action => 'index',
+ :defaults => { :year => nil },
+ :requirements => { :year => /\d{4}/ }
+ )
+
+ map.resources :people
+ map.connect 'legacy/people', :controller => 'people', :action => 'index', :legacy => 'true'
+
+ map.connect 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
+ map.connect 'id_default/:id', :controller => 'foo', :action => 'id_default', :id => 1
+ map.connect 'get_or_post', :controller => 'foo', :action => 'get_or_post', :conditions => { :method => [:get, :post] }
+ map.connect 'optional/:optional', :controller => 'posts', :action => 'index'
+ map.project 'projects/:project_id', :controller => 'project'
+ map.connect 'clients', :controller => 'projects', :action => 'index'
+
+ map.connect 'ignorecase/geocode/:postalcode', :controller => 'geocode',
+ :action => 'show', :postalcode => /hx\d\d-\d[a-z]{2}/i
+ map.geocode 'extended/geocode/:postalcode', :controller => 'geocode',
+ :action => 'show',:requirements => {
+ :postalcode => /# Postcode format
+ \d{5} #Prefix
+ (-\d{4})? #Suffix
+ /x
+ }
+
+ map.connect '', :controller => 'news', :format => nil
+ map.connect 'news.:format', :controller => 'news'
+
+ map.connect 'comment/:id/:action', :controller => 'comments', :action => 'show'
+ map.connect 'ws/:controller/:action/:id', :ws => true
+ map.connect 'account/:action', :controller => :account, :action => :subscription
+ map.connect 'pages/:page_id/:controller/:action/:id'
+ map.connect ':controller/ping', :action => 'ping'
+ map.connect ':controller/:action/:id'
+ }
+
+ def setup
+ @routes = ActionController::Routing::RouteSet.new
+ @routes.draw(&Mapping)
+ end
+
+ def test_add_route
+ @routes.clear!
+
+ assert_raise(ActionController::RoutingError) do
+ @routes.draw do |map|
+ map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default)
+ map.connect ':controller/:action/:id'
+ end
+ end
+ end
+
+ def test_recognize_path
+ assert_equal({:controller => 'admin/users', :action => 'index'}, @routes.recognize_path('/admin/users', :method => :get))
+ assert_equal({:controller => 'admin/users', :action => 'create'}, @routes.recognize_path('/admin/users', :method => :post))
+ assert_equal({:controller => 'admin/users', :action => 'new'}, @routes.recognize_path('/admin/users/new', :method => :get))
+ assert_equal({:controller => 'admin/users', :action => 'show', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :get))
+ assert_equal({:controller => 'admin/users', :action => 'update', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :put))
+ assert_equal({:controller => 'admin/users', :action => 'destroy', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :delete))
+ assert_equal({:controller => 'admin/users', :action => 'edit', :id => '1'}, @routes.recognize_path('/admin/users/1/edit', :method => :get))
+
+ assert_equal({:controller => 'admin/posts', :action => 'index'}, @routes.recognize_path('/admin/posts', :method => :get))
+ assert_equal({:controller => 'admin/posts', :action => 'new'}, @routes.recognize_path('/admin/posts/new', :method => :get))
+
+ assert_equal({:controller => 'api/users', :action => 'index'}, @routes.recognize_path('/api', :method => :get))
+ assert_equal({:controller => 'api/users', :action => 'index'}, @routes.recognize_path('/api/', :method => :get))
+
+ assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009'}, @routes.recognize_path('/blog/2009', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009', :month => '01'}, @routes.recognize_path('/blog/2009/01', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009', :month => '01', :day => '01'}, @routes.recognize_path('/blog/2009/01/01', :method => :get))
+ assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/blog/123456789', :method => :get) }
+
+ assert_equal({:controller => 'archive', :action => 'index', :year => '2010'}, @routes.recognize_path('/archive/2010'))
+ assert_equal({:controller => 'archive', :action => 'index'}, @routes.recognize_path('/archive'))
+ assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/archive/january') }
+
+ assert_equal({:controller => 'people', :action => 'index'}, @routes.recognize_path('/people', :method => :get))
+ assert_equal({:controller => 'people', :action => 'index', :format => 'xml'}, @routes.recognize_path('/people.xml', :method => :get))
+ assert_equal({:controller => 'people', :action => 'create'}, @routes.recognize_path('/people', :method => :post))
+ assert_equal({:controller => 'people', :action => 'new'}, @routes.recognize_path('/people/new', :method => :get))
+ assert_equal({:controller => 'people', :action => 'show', :id => '1'}, @routes.recognize_path('/people/1', :method => :get))
+ assert_equal({:controller => 'people', :action => 'show', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1.xml', :method => :get))
+ assert_equal({:controller => 'people', :action => 'update', :id => '1'}, @routes.recognize_path('/people/1', :method => :put))
+ assert_equal({:controller => 'people', :action => 'destroy', :id => '1'}, @routes.recognize_path('/people/1', :method => :delete))
+ assert_equal({:controller => 'people', :action => 'edit', :id => '1'}, @routes.recognize_path('/people/1/edit', :method => :get))
+ assert_equal({:controller => 'people', :action => 'edit', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1/edit.xml', :method => :get))
+
+ assert_equal({:controller => 'symbols', :action => 'show', :name => :as_symbol}, @routes.recognize_path('/symbols'))
+ assert_equal({:controller => 'foo', :action => 'id_default', :id => '1'}, @routes.recognize_path('/id_default/1'))
+ assert_equal({:controller => 'foo', :action => 'id_default', :id => '2'}, @routes.recognize_path('/id_default/2'))
+ assert_equal({:controller => 'foo', :action => 'id_default', :id => '1'}, @routes.recognize_path('/id_default'))
+ assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :get))
+ assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :post))
+ assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :put) }
+ assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :delete) }
+
+ assert_equal({:controller => 'posts', :action => 'index', :optional => 'bar'}, @routes.recognize_path('/optional/bar'))
+ assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/optional') }
+
+ assert_equal({:controller => 'posts', :action => 'show', :id => '1', :ws => true}, @routes.recognize_path('/ws/posts/show/1', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'list', :ws => true}, @routes.recognize_path('/ws/posts/list', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'index', :ws => true}, @routes.recognize_path('/ws/posts', :method => :get))
+
+ assert_equal({:controller => 'account', :action => 'subscription'}, @routes.recognize_path('/account', :method => :get))
+ assert_equal({:controller => 'account', :action => 'subscription'}, @routes.recognize_path('/account/subscription', :method => :get))
+ assert_equal({:controller => 'account', :action => 'billing'}, @routes.recognize_path('/account/billing', :method => :get))
+
+ assert_equal({:page_id => '1', :controller => 'notes', :action => 'index'}, @routes.recognize_path('/pages/1/notes', :method => :get))
+ assert_equal({:page_id => '1', :controller => 'notes', :action => 'list'}, @routes.recognize_path('/pages/1/notes/list', :method => :get))
+ assert_equal({:page_id => '1', :controller => 'notes', :action => 'show', :id => '2'}, @routes.recognize_path('/pages/1/notes/show/2', :method => :get))
+
+ assert_equal({:controller => 'posts', :action => 'ping'}, @routes.recognize_path('/posts/ping', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'index'}, @routes.recognize_path('/posts', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'index'}, @routes.recognize_path('/posts/index', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'show'}, @routes.recognize_path('/posts/show', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'show', :id => '1'}, @routes.recognize_path('/posts/show/1', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'create'}, @routes.recognize_path('/posts/create', :method => :post))
+
+ assert_equal({:controller => 'geocode', :action => 'show', :postalcode => 'hx12-1az'}, @routes.recognize_path('/ignorecase/geocode/hx12-1az'))
+ assert_equal({:controller => 'geocode', :action => 'show', :postalcode => 'hx12-1AZ'}, @routes.recognize_path('/ignorecase/geocode/hx12-1AZ'))
+ assert_equal({:controller => 'geocode', :action => 'show', :postalcode => '12345-1234'}, @routes.recognize_path('/extended/geocode/12345-1234'))
+ assert_equal({:controller => 'geocode', :action => 'show', :postalcode => '12345'}, @routes.recognize_path('/extended/geocode/12345'))
+
+ assert_equal({:controller => 'news', :action => 'index', :format => nil}, @routes.recognize_path('/', :method => :get))
+ assert_equal({:controller => 'news', :action => 'index', :format => 'rss'}, @routes.recognize_path('/news.rss', :method => :get))
+
+ assert_raise(ActionController::RoutingError) { @routes.recognize_path('/none', :method => :get) }
+ end
+
+ def test_generate
+ assert_equal '/admin/users', @routes.generate(:use_route => 'admin_users')
+ assert_equal '/admin/users', @routes.generate(:controller => 'admin/users')
+ assert_equal '/admin/users', @routes.generate(:controller => 'admin/users', :action => 'index')
+ assert_equal '/admin/users', @routes.generate({:action => 'index'}, {:controller => 'admin/users'})
+ assert_equal '/admin/users', @routes.generate({:controller => 'users', :action => 'index'}, {:controller => 'admin/accounts'})
+ assert_equal '/people', @routes.generate({:controller => '/people', :action => 'index'}, {:controller => 'admin/accounts'})
+
+ assert_equal '/admin/posts', @routes.generate({:controller => 'admin/posts'})
+ assert_equal '/admin/posts/new', @routes.generate({:controller => 'admin/posts', :action => 'new'})
+
+ assert_equal '/blog/2009', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009)
+ assert_equal '/blog/2009/1', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009, :month => 1)
+ assert_equal '/blog/2009/1/1', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1)
+
+ assert_equal '/archive/2010', @routes.generate(:controller => 'archive', :action => 'index', :year => '2010')
+ assert_equal '/archive', @routes.generate(:controller => 'archive', :action => 'index')
+ assert_equal '/archive?year=january', @routes.generate(:controller => 'archive', :action => 'index', :year => 'january')
+
+ assert_equal '/people', @routes.generate(:use_route => 'people')
+ assert_equal '/people', @routes.generate(:use_route => 'people', :controller => 'people', :action => 'index')
+ assert_equal '/people.xml', @routes.generate(:use_route => 'people', :controller => 'people', :action => 'index', :format => 'xml')
+ assert_equal '/people', @routes.generate({:use_route => 'people', :controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'index'})
+ assert_equal '/people', @routes.generate(:controller => 'people')
+ assert_equal '/people', @routes.generate(:controller => 'people', :action => 'index')
+ assert_equal '/people', @routes.generate({:action => 'index'}, {:controller => 'people'})
+ assert_equal '/people', @routes.generate({:action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'})
+ assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'})
+ assert_equal '/people', @routes.generate({}, {:controller => 'people', :action => 'index'})
+ assert_equal '/people/1', @routes.generate({:controller => 'people', :action => 'show'}, {:controller => 'people', :action => 'show', :id => '1'})
+ assert_equal '/people/new', @routes.generate(:use_route => 'new_person')
+ assert_equal '/people/new', @routes.generate(:controller => 'people', :action => 'new')
+ assert_equal '/people/1', @routes.generate(:use_route => 'person', :id => '1')
+ assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => '1')
+ assert_equal '/people/1.xml', @routes.generate(:controller => 'people', :action => 'show', :id => '1', :format => 'xml')
+ assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => 1)
+ assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => Model.new('1'))
+ assert_equal '/people/1', @routes.generate({:action => 'show', :id => '1'}, {:controller => 'people', :action => 'index'})
+ assert_equal '/people/1', @routes.generate({:action => 'show', :id => 1}, {:controller => 'people', :action => 'show', :id => '1'})
+ # assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'index', :id => '1'})
+ assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'})
+ assert_equal '/people/1', @routes.generate({}, {:controller => 'people', :action => 'show', :id => '1'})
+ assert_equal '/people/1', @routes.generate({:controller => 'people', :action => 'show'}, {:controller => 'people', :action => 'index', :id => '1'})
+ assert_equal '/people/1/edit', @routes.generate(:controller => 'people', :action => 'edit', :id => '1')
+ assert_equal '/people/1/edit.xml', @routes.generate(:controller => 'people', :action => 'edit', :id => '1', :format => 'xml')
+ assert_equal '/people/1/edit', @routes.generate(:use_route => 'edit_person', :id => '1')
+ assert_equal '/people/1?legacy=true', @routes.generate(:controller => 'people', :action => 'show', :id => '1', :legacy => 'true')
+ assert_equal '/people?legacy=true', @routes.generate(:controller => 'people', :action => 'index', :legacy => 'true')
+
+ assert_equal '/id_default/2', @routes.generate(:controller => 'foo', :action => 'id_default', :id => '2')
+ assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default', :id => '1')
+ assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default', :id => 1)
+ assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default')
+ assert_equal '/optional/bar', @routes.generate(:controller => 'posts', :action => 'index', :optional => 'bar')
+ assert_equal '/posts', @routes.generate(:controller => 'posts', :action => 'index')
+
+ assert_equal '/project', @routes.generate({:controller => 'project', :action => 'index'})
+ assert_equal '/projects/1', @routes.generate({:controller => 'project', :action => 'index', :project_id => '1'})
+ assert_equal '/projects/1', @routes.generate({:controller => 'project', :action => 'index'}, {:project_id => '1'})
+ assert_raise(ActionController::RoutingError) { @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index'}) }
+ assert_equal '/projects/1', @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1'})
+ assert_equal '/projects/1', @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index'}, {:project_id => '1'})
+
+ assert_equal '/clients', @routes.generate(:controller => 'projects', :action => 'index')
+ assert_equal '/clients?project_id=1', @routes.generate(:controller => 'projects', :action => 'index', :project_id => '1')
+ assert_equal '/clients', @routes.generate({:controller => 'projects', :action => 'index'}, {:project_id => '1'})
+ assert_equal '/clients', @routes.generate({:action => 'index'}, {:controller => 'projects', :action => 'index', :project_id => '1'})
+
+ assert_equal '/comment/20', @routes.generate({:id => 20}, {:controller => 'comments', :action => 'show'})
+ assert_equal '/comment/20', @routes.generate(:controller => 'comments', :id => 20, :action => 'show')
+ assert_equal '/comments/boo', @routes.generate(:controller => 'comments', :action => 'boo')
+
+ assert_equal '/ws/posts/show/1', @routes.generate(:controller => 'posts', :action => 'show', :id => '1', :ws => true)
+ assert_equal '/ws/posts', @routes.generate(:controller => 'posts', :action => 'index', :ws => true)
+
+ assert_equal '/account', @routes.generate(:controller => 'account', :action => 'subscription')
+ assert_equal '/account/billing', @routes.generate(:controller => 'account', :action => 'billing')
+
+ assert_equal '/pages/1/notes/show/1', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'show', :id => '1')
+ assert_equal '/pages/1/notes/list', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'list')
+ assert_equal '/pages/1/notes', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'index')
+ assert_equal '/pages/1/notes', @routes.generate(:page_id => '1', :controller => 'notes')
+ assert_equal '/notes', @routes.generate(:page_id => nil, :controller => 'notes')
+ assert_equal '/notes', @routes.generate(:controller => 'notes')
+ assert_equal '/notes/print', @routes.generate(:controller => 'notes', :action => 'print')
+ assert_equal '/notes/print', @routes.generate({}, {:controller => 'notes', :action => 'print'})
+
+ assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1'})
+ assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1', :foo => 'bar'})
+ assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1'})
+ assert_equal '/notes/index/1', @routes.generate({:action => 'index'}, {:controller => 'notes', :id => '1'})
+ assert_equal '/notes/index/1', @routes.generate({}, {:controller => 'notes', :id => '1'})
+ assert_equal '/notes/show/1', @routes.generate({}, {:controller => 'notes', :action => 'show', :id => '1'})
+ assert_equal '/notes/index/1', @routes.generate({:controller => 'notes', :id => '1'}, {:foo => 'bar'})
+ assert_equal '/posts', @routes.generate({:controller => 'posts'}, {:controller => 'notes', :action => 'show', :id => '1'})
+ assert_equal '/notes/list', @routes.generate({:action => 'list'}, {:controller => 'notes', :action => 'show', :id => '1'})
+
+ assert_equal '/posts/ping', @routes.generate(:controller => 'posts', :action => 'ping')
+ assert_equal '/posts/show/1', @routes.generate(:controller => 'posts', :action => 'show', :id => '1')
+ assert_equal '/posts', @routes.generate(:controller => 'posts')
+ assert_equal '/posts', @routes.generate(:controller => 'posts', :action => 'index')
+ assert_equal '/posts', @routes.generate({:controller => 'posts'}, {:controller => 'posts', :action => 'index'})
+ assert_equal '/posts/create', @routes.generate({:action => 'create'}, {:controller => 'posts'})
+ assert_equal '/posts?foo=bar', @routes.generate(:controller => 'posts', :foo => 'bar')
+ assert_equal '/posts?foo%5B%5D=bar&foo%5B%5D=baz', @routes.generate(:controller => 'posts', :foo => ['bar', 'baz'])
+ assert_equal '/posts?page=2', @routes.generate(:controller => 'posts', :page => 2)
+ assert_equal '/posts?q%5Bfoo%5D%5Ba%5D=b', @routes.generate(:controller => 'posts', :q => { :foo => { :a => 'b'}})
+
+ assert_equal '/', @routes.generate(:controller => 'news', :action => 'index')
+ assert_equal '/', @routes.generate(:controller => 'news', :action => 'index', :format => nil)
+ assert_equal '/news.rss', @routes.generate(:controller => 'news', :action => 'index', :format => 'rss')
+
+
+ assert_raise(ActionController::RoutingError) { @routes.generate({:action => 'index'}) }
+ end
+
+ def test_generate_extras
+ assert_equal ['/people', []], @routes.generate_extras(:controller => 'people')
+ assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :foo => 'bar')
+ assert_equal ['/people', []], @routes.generate_extras(:controller => 'people', :action => 'index')
+ assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :action => 'index', :foo => 'bar')
+ assert_equal ['/people/new', []], @routes.generate_extras(:controller => 'people', :action => 'new')
+ assert_equal ['/people/new', [:foo]], @routes.generate_extras(:controller => 'people', :action => 'new', :foo => 'bar')
+ assert_equal ['/people/1', []], @routes.generate_extras(:controller => 'people', :action => 'show', :id => '1')
+ assert_equal ['/people/1', [:bar, :foo]], sort_extras!(@routes.generate_extras(:controller => 'people', :action => 'show', :id => '1', :foo => '2', :bar => '3'))
+ assert_equal ['/people', [:person]], @routes.generate_extras(:controller => 'people', :action => 'create', :person => { :first_name => 'Josh', :last_name => 'Peek' })
+ assert_equal ['/people', [:people]], @routes.generate_extras(:controller => 'people', :action => 'create', :people => ['Josh', 'Dave'])
+
+ assert_equal ['/posts/show/1', []], @routes.generate_extras(:controller => 'posts', :action => 'show', :id => '1')
+ assert_equal ['/posts/show/1', [:bar, :foo]], sort_extras!(@routes.generate_extras(:controller => 'posts', :action => 'show', :id => '1', :foo => '2', :bar => '3'))
+ assert_equal ['/posts', []], @routes.generate_extras(:controller => 'posts', :action => 'index')
+ assert_equal ['/posts', [:foo]], @routes.generate_extras(:controller => 'posts', :action => 'index', :foo => 'bar')
+ end
+
+ def test_extras
+ params = {:controller => 'people'}
+ assert_equal [], @routes.extra_keys(params)
+ assert_equal({:controller => 'people'}, params)
+
+ params = {:controller => 'people', :foo => 'bar'}
+ assert_equal [:foo], @routes.extra_keys(params)
+ assert_equal({:controller => 'people', :foo => 'bar'}, params)
+
+ params = {:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}
+ assert_equal [:person], @routes.extra_keys(params)
+ assert_equal({:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}, params)
+ end
+
+ private
+ def sort_extras!(extras)
+ if extras.length == 2
+ extras[1].sort! { |a, b| a.to_s <=> b.to_s }
+ end
+ extras
+ end
+
+ def assert_raise(e)
+ result = yield
+ flunk "Did not raise #{e}, but returned #{result.inspect}"
+ rescue e
+ assert true
+ end
+end
diff --git a/actionpack/test/lib/controller/fake_controllers.rb b/actionpack/test/lib/controller/fake_controllers.rb
index c993836a61..250327e6dc 100644
--- a/actionpack/test/lib/controller/fake_controllers.rb
+++ b/actionpack/test/lib/controller/fake_controllers.rb
@@ -5,9 +5,10 @@ class NotAController; end
module Admin
class << self; alias_method :const_available?, :const_defined?; end
- class UserController < ActionController::Base; end
class NewsFeedController < ActionController::Base; end
+ class PostsController < ActionController::Base; end
class StuffController < ActionController::Base; end
+ class UserController < ActionController::Base; end
end
module Api
@@ -25,7 +26,9 @@ class ElsewhereController < ActionController::Base; end
class FooController < ActionController::Base; end
class HiController < ActionController::Base; end
class ImageController < ActionController::Base; end
+class NotesController < ActionController::Base; end
class PeopleController < ActionController::Base; end
+class PostsController < ActionController::Base; end
class SessionsController < ActionController::Base; end
class StuffController < ActionController::Base; end
class SubpathBooksController < ActionController::Base; end
--
cgit v1.2.3
From a74022ecd3e078f55ed6049a96565119dc540ff5 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 20 Oct 2009 10:14:46 -0500
Subject: Move Routing into AD
---
actionpack/lib/action_controller.rb | 13 +-
actionpack/lib/action_controller/deprecated.rb | 3 +
.../lib/action_controller/polymorphic_routes.rb | 210 +++++++
actionpack/lib/action_controller/routing.rb | 381 -----------
.../routing/polymorphic_routes.rb | 210 -------
.../lib/action_controller/routing/resources.rb | 685 --------------------
.../lib/action_controller/routing/route_set.rb | 699 ---------------------
.../lib/action_controller/routing/url_rewriter.rb | 204 ------
actionpack/lib/action_controller/url_rewriter.rb | 204 ++++++
actionpack/lib/action_dispatch.rb | 2 +
actionpack/lib/action_dispatch/routing.rb | 381 +++++++++++
.../lib/action_dispatch/routing/resources.rb | 687 ++++++++++++++++++++
.../lib/action_dispatch/routing/route_set.rb | 699 +++++++++++++++++++++
actionpack/test/controller/resources_test.rb | 12 +-
14 files changed, 2197 insertions(+), 2193 deletions(-)
create mode 100644 actionpack/lib/action_controller/polymorphic_routes.rb
delete mode 100644 actionpack/lib/action_controller/routing.rb
delete mode 100644 actionpack/lib/action_controller/routing/polymorphic_routes.rb
delete mode 100644 actionpack/lib/action_controller/routing/resources.rb
delete mode 100644 actionpack/lib/action_controller/routing/route_set.rb
delete mode 100644 actionpack/lib/action_controller/routing/url_rewriter.rb
create mode 100644 actionpack/lib/action_controller/url_rewriter.rb
create mode 100644 actionpack/lib/action_dispatch/routing.rb
create mode 100644 actionpack/lib/action_dispatch/routing/resources.rb
create mode 100644 actionpack/lib/action_dispatch/routing/route_set.rb
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 809e5f2ad4..b5091f39f9 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -18,22 +18,21 @@ module ActionController
autoload :Testing, "action_controller/metal/testing"
autoload :UrlFor, "action_controller/metal/url_for"
- # Ported modules
- # require 'action_controller/routing'
autoload :Caching, 'action_controller/caching'
autoload :Dispatcher, 'action_controller/dispatch/dispatcher'
autoload :Integration, 'action_controller/deprecated/integration_test'
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
autoload :MimeResponds, 'action_controller/metal/mime_responds'
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
- autoload :PolymorphicRoutes, 'action_controller/routing/polymorphic_routes'
+ autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
autoload :RecordIdentifier, 'action_controller/record_identifier'
- autoload :Resources, 'action_controller/routing/resources'
+ autoload :Resources, 'action_controller/deprecated'
+ autoload :Routing, 'action_controller/deprecated'
autoload :SessionManagement, 'action_controller/metal/session_management'
autoload :TestCase, 'action_controller/testing/test_case'
autoload :TestProcess, 'action_controller/testing/process'
- autoload :UrlRewriter, 'action_controller/routing/url_rewriter'
- autoload :UrlWriter, 'action_controller/routing/url_rewriter'
+ autoload :UrlRewriter, 'action_controller/url_rewriter'
+ autoload :UrlWriter, 'action_controller/url_rewriter'
autoload :Verification, 'action_controller/metal/verification'
autoload :Flash, 'action_controller/metal/flash'
@@ -54,8 +53,6 @@ module ActionController
autoload :RenderError, 'action_controller/metal/exceptions'
autoload :SessionOverflowError, 'action_controller/metal/exceptions'
autoload :UnknownHttpMethod, 'action_controller/metal/exceptions'
-
- autoload :Routing, 'action_controller/routing'
end
autoload :HTML, 'action_controller/vendor/html-scanner'
diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb
index d98e9ac7bd..23fe6a4c3a 100644
--- a/actionpack/lib/action_controller/deprecated.rb
+++ b/actionpack/lib/action_controller/deprecated.rb
@@ -1,2 +1,5 @@
ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
+ActionController::Routing = ActionDispatch::Routing
+ActionDispatch::Resources = ActionDispatch::Routing::Resources
+ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new
diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
new file mode 100644
index 0000000000..2adf3575a7
--- /dev/null
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -0,0 +1,210 @@
+module ActionController
+ # Polymorphic URL helpers are methods for smart resolution to a named route call when
+ # given an Active Record model instance. They are to be used in combination with
+ # ActionController::Resources.
+ #
+ # These methods are useful when you want to generate correct URL or path to a RESTful
+ # resource without having to know the exact type of the record in question.
+ #
+ # Nested resources and/or namespaces are also supported, as illustrated in the example:
+ #
+ # polymorphic_url([:admin, @article, @comment])
+ #
+ # results in:
+ #
+ # admin_article_comment_url(@article, @comment)
+ #
+ # == Usage within the framework
+ #
+ # Polymorphic URL helpers are used in a number of places throughout the Rails framework:
+ #
+ # * url_for, so you can use it with a record as the argument, e.g.
+ # url_for(@article);
+ # * ActionView::Helpers::FormHelper uses polymorphic_path, so you can write
+ # form_for(@article) without having to specify :url parameter for the form
+ # action;
+ # * redirect_to (which, in fact, uses url_for) so you can write
+ # redirect_to(post) in your controllers;
+ # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
+ # for feed entries.
+ #
+ # == Prefixed polymorphic helpers
+ #
+ # In addition to polymorphic_url and polymorphic_path methods, a
+ # number of prefixed helpers are available as a shorthand to :action => "..."
+ # in options. Those are:
+ #
+ # * edit_polymorphic_url, edit_polymorphic_path
+ # * new_polymorphic_url, new_polymorphic_path
+ #
+ # Example usage:
+ #
+ # edit_polymorphic_path(@post) # => "/posts/1/edit"
+ # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
+ module PolymorphicRoutes
+ # Constructs a call to a named RESTful route for the given record and returns the
+ # resulting URL string. For example:
+ #
+ # # calls post_url(post)
+ # polymorphic_url(post) # => "http://example.com/posts/1"
+ # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
+ # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
+ # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
+ # polymorphic_url(Comment) # => "http://example.com/comments"
+ #
+ # ==== Options
+ #
+ # * :action - Specifies the action prefix for the named route:
+ # :new or :edit. Default is no prefix.
+ # * :routing_type - Allowed values are :path or :url.
+ # Default is :url.
+ #
+ # ==== Examples
+ #
+ # # an Article record
+ # polymorphic_url(record) # same as article_url(record)
+ #
+ # # a Comment record
+ # polymorphic_url(record) # same as comment_url(record)
+ #
+ # # it recognizes new records and maps to the collection
+ # record = Comment.new
+ # polymorphic_url(record) # same as comments_url()
+ #
+ # # the class of a record will also map to the collection
+ # polymorphic_url(Comment) # same as comments_url()
+ #
+ def polymorphic_url(record_or_hash_or_array, options = {})
+ if record_or_hash_or_array.kind_of?(Array)
+ record_or_hash_or_array = record_or_hash_or_array.compact
+ record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
+ end
+
+ record = extract_record(record_or_hash_or_array)
+ record = record.to_model if record.respond_to?(:to_model)
+ namespace = extract_namespace(record_or_hash_or_array)
+
+ args = case record_or_hash_or_array
+ when Hash; [ record_or_hash_or_array ]
+ when Array; record_or_hash_or_array.dup
+ else [ record_or_hash_or_array ]
+ end
+
+ inflection = if options[:action].to_s == "new"
+ args.pop
+ :singular
+ elsif (record.respond_to?(:new_record?) && record.new_record?) ||
+ (record.respond_to?(:destroyed?) && record.destroyed?)
+ args.pop
+ :plural
+ elsif record.is_a?(Class)
+ args.pop
+ :plural
+ else
+ :singular
+ end
+
+ args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
+
+ named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
+
+ url_options = options.except(:action, :routing_type)
+ unless url_options.empty?
+ args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
+ end
+
+ __send__(named_route, *args)
+ end
+
+ # Returns the path component of a URL for the given record. It uses
+ # polymorphic_url with :routing_type => :path.
+ def polymorphic_path(record_or_hash_or_array, options = {})
+ polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
+ end
+
+ %w(edit new).each do |action|
+ module_eval <<-EOT, __FILE__, __LINE__
+ def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
+ end # end
+ #
+ def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
+ end # end
+ EOT
+ end
+
+ def formatted_polymorphic_url(record_or_hash, options = {})
+ ActiveSupport::Deprecation.warn("formatted_polymorphic_url has been deprecated. Please pass :format to the polymorphic_url method instead", caller)
+ options[:format] = record_or_hash.pop if Array === record_or_hash
+ polymorphic_url(record_or_hash, options)
+ end
+
+ def formatted_polymorphic_path(record_or_hash, options = {})
+ ActiveSupport::Deprecation.warn("formatted_polymorphic_path has been deprecated. Please pass :format to the polymorphic_path method instead", caller)
+ options[:format] = record_or_hash.pop if record_or_hash === Array
+ polymorphic_url(record_or_hash, options.merge(:routing_type => :path))
+ end
+
+ private
+ def action_prefix(options)
+ options[:action] ? "#{options[:action]}_" : ''
+ end
+
+ def routing_type(options)
+ options[:routing_type] || :url
+ end
+
+ def build_named_route_call(records, namespace, inflection, options = {})
+ unless records.is_a?(Array)
+ record = extract_record(records)
+ route = ''
+ else
+ record = records.pop
+ route = records.inject("") do |string, parent|
+ if parent.is_a?(Symbol) || parent.is_a?(String)
+ string << "#{parent}_"
+ else
+ string << "#{RecordIdentifier.__send__("plural_class_name", parent)}".singularize
+ string << "_"
+ end
+ end
+ end
+
+ if record.is_a?(Symbol) || record.is_a?(String)
+ route << "#{record}_"
+ else
+ route << "#{RecordIdentifier.__send__("plural_class_name", record)}"
+ route = route.singularize if inflection == :singular
+ route << "_"
+ end
+
+ action_prefix(options) + namespace + route + routing_type(options).to_s
+ end
+
+ def extract_record(record_or_hash_or_array)
+ case record_or_hash_or_array
+ when Array; record_or_hash_or_array.last
+ when Hash; record_or_hash_or_array[:id]
+ else record_or_hash_or_array
+ end
+ end
+
+ # Remove the first symbols from the array and return the url prefix
+ # implied by those symbols.
+ def extract_namespace(record_or_hash_or_array)
+ return "" unless record_or_hash_or_array.is_a?(Array)
+
+ namespace_keys = []
+ while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol)
+ namespace_keys << record_or_hash_or_array.shift
+ end
+
+ namespace_keys.map {|k| "#{k}_"}.join
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb
deleted file mode 100644
index 979a4ad8c9..0000000000
--- a/actionpack/lib/action_controller/routing.rb
+++ /dev/null
@@ -1,381 +0,0 @@
-require 'active_support/core_ext/object/conversions'
-require 'active_support/core_ext/boolean/conversions'
-require 'active_support/core_ext/nil/conversions'
-require 'active_support/core_ext/regexp'
-require 'action_controller/routing/route_set'
-
-module ActionController
- # == Routing
- #
- # The routing module provides URL rewriting in native Ruby. It's a way to
- # redirect incoming requests to controllers and actions. This replaces
- # mod_rewrite rules. Best of all, Rails' Routing works with any web server.
- # Routes are defined in config/routes.rb.
- #
- # Consider the following route, installed by Rails when you generate your
- # application:
- #
- # map.connect ':controller/:action/:id'
- #
- # This route states that it expects requests to consist of a
- # :controller followed by an :action that in turn is fed
- # some :id.
- #
- # Suppose you get an incoming request for /blog/edit/22, you'll end up
- # with:
- #
- # params = { :controller => 'blog',
- # :action => 'edit',
- # :id => '22'
- # }
- #
- # Think of creating routes as drawing a map for your requests. The map tells
- # them where to go based on some predefined pattern:
- #
- # ActionController::Routing::Routes.draw do |map|
- # Pattern 1 tells some request to go to one place
- # Pattern 2 tell them to go to another
- # ...
- # end
- #
- # The following symbols are special:
- #
- # :controller maps to your controller name
- # :action maps to an action with your controllers
- #
- # Other names simply map to a parameter as in the case of :id.
- #
- # == Route priority
- #
- # Not all routes are created equally. Routes have priority defined by the
- # order of appearance of the routes in the config/routes.rb file. The priority goes
- # from top to bottom. The last route in that file is at the lowest priority
- # and will be applied last. If no route matches, 404 is returned.
- #
- # Within blocks, the empty pattern is at the highest priority.
- # In practice this works out nicely:
- #
- # ActionController::Routing::Routes.draw do |map|
- # map.with_options :controller => 'blog' do |blog|
- # blog.show '', :action => 'list'
- # end
- # map.connect ':controller/:action/:view'
- # end
- #
- # In this case, invoking blog controller (with an URL like '/blog/')
- # without parameters will activate the 'list' action by default.
- #
- # == Defaults routes and default parameters
- #
- # Setting a default route is straightforward in Rails - you simply append a
- # Hash at the end of your mapping to set any default parameters.
- #
- # Example:
- #
- # ActionController::Routing:Routes.draw do |map|
- # map.connect ':controller/:action/:id', :controller => 'blog'
- # end
- #
- # This sets up +blog+ as the default controller if no other is specified.
- # This means visiting '/' would invoke the blog controller.
- #
- # More formally, you can include arbitrary parameters in the route, thus:
- #
- # map.connect ':controller/:action/:id', :action => 'show', :page => 'Dashboard'
- #
- # This will pass the :page parameter to all incoming requests that match this route.
- #
- # Note: The default routes, as provided by the Rails generator, make all actions in every
- # controller accessible via GET requests. You should consider removing them or commenting
- # them out if you're using named routes and resources.
- #
- # == Named routes
- #
- # Routes can be named with the syntax map.name_of_route options,
- # allowing for easy reference within your source as +name_of_route_url+
- # for the full URL and +name_of_route_path+ for the URI path.
- #
- # Example:
- #
- # # In routes.rb
- # map.login 'login', :controller => 'accounts', :action => 'login'
- #
- # # With render, redirect_to, tests, etc.
- # redirect_to login_url
- #
- # Arguments can be passed as well.
- #
- # redirect_to show_item_path(:id => 25)
- #
- # Use map.root as a shorthand to name a route for the root path "".
- #
- # # In routes.rb
- # map.root :controller => 'blogs'
- #
- # # would recognize http://www.example.com/ as
- # params = { :controller => 'blogs', :action => 'index' }
- #
- # # and provide these named routes
- # root_url # => 'http://www.example.com/'
- # root_path # => ''
- #
- # You can also specify an already-defined named route in your map.root call:
- #
- # # In routes.rb
- # map.new_session :controller => 'sessions', :action => 'new'
- # map.root :new_session
- #
- # Note: when using +with_options+, the route is simply named after the
- # method you call on the block parameter rather than map.
- #
- # # In routes.rb
- # map.with_options :controller => 'blog' do |blog|
- # blog.show '', :action => 'list'
- # blog.delete 'delete/:id', :action => 'delete'
- # blog.edit 'edit/:id', :action => 'edit'
- # end
- #
- # # provides named routes for show, delete, and edit
- # link_to @article.title, show_path(:id => @article.id)
- #
- # == Pretty URLs
- #
- # Routes can generate pretty URLs. For example:
- #
- # map.connect 'articles/:year/:month/:day',
- # :controller => 'articles',
- # :action => 'find_by_date',
- # :year => /\d{4}/,
- # :month => /\d{1,2}/,
- # :day => /\d{1,2}/
- #
- # Using the route above, the URL "http://localhost:3000/articles/2005/11/06"
- # maps to
- #
- # params = {:year => '2005', :month => '11', :day => '06'}
- #
- # == Regular Expressions and parameters
- # You can specify a regular expression to define a format for a parameter.
- #
- # map.geocode 'geocode/:postalcode', :controller => 'geocode',
- # :action => 'show', :postalcode => /\d{5}(-\d{4})?/
- #
- # or, more formally:
- #
- # map.geocode 'geocode/:postalcode', :controller => 'geocode',
- # :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ }
- #
- # Formats can include the 'ignorecase' and 'extended syntax' regular
- # expression modifiers:
- #
- # map.geocode 'geocode/:postalcode', :controller => 'geocode',
- # :action => 'show', :postalcode => /hx\d\d\s\d[a-z]{2}/i
- #
- # map.geocode 'geocode/:postalcode', :controller => 'geocode',
- # :action => 'show',:requirements => {
- # :postalcode => /# Postcode format
- # \d{5} #Prefix
- # (-\d{4})? #Suffix
- # /x
- # }
- #
- # Using the multiline match modifier will raise an ArgumentError.
- # Encoding regular expression modifiers are silently ignored. The
- # match will always use the default encoding or ASCII.
- #
- # == Route globbing
- #
- # Specifying *[string] as part of a rule like:
- #
- # map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
- #
- # will glob all remaining parts of the route that were not recognized earlier.
- # The globbed values are in params[:path] as an array of path segments.
- #
- # == Route conditions
- #
- # With conditions you can define restrictions on routes. Currently the only valid condition is :method.
- #
- # * :method - Allows you to specify which method can access the route. Possible values are :post,
- # :get, :put, :delete and :any. The default value is :any,
- # :any means that any method can access the route.
- #
- # Example:
- #
- # map.connect 'post/:id', :controller => 'posts', :action => 'show',
- # :conditions => { :method => :get }
- # map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
- # :conditions => { :method => :post }
- #
- # Now, if you POST to /posts/:id, it will route to the create_comment action. A GET on the same
- # URL will route to the show action.
- #
- # == Reloading routes
- #
- # You can reload routes if you feel you must:
- #
- # ActionController::Routing::Routes.reload
- #
- # This will clear all named routes and reload routes.rb if the file has been modified from
- # last load. To absolutely force reloading, use reload!.
- #
- # == Testing Routes
- #
- # The two main methods for testing your routes:
- #
- # === +assert_routing+
- #
- # def test_movie_route_properly_splits
- # opts = {:controller => "plugin", :action => "checkout", :id => "2"}
- # assert_routing "plugin/checkout/2", opts
- # end
- #
- # +assert_routing+ lets you test whether or not the route properly resolves into options.
- #
- # === +assert_recognizes+
- #
- # def test_route_has_options
- # opts = {:controller => "plugin", :action => "show", :id => "12"}
- # assert_recognizes opts, "/plugins/show/12"
- # end
- #
- # Note the subtle difference between the two: +assert_routing+ tests that
- # a URL fits options while +assert_recognizes+ tests that a URL
- # breaks into parameters properly.
- #
- # In tests you can simply pass the URL or named route to +get+ or +post+.
- #
- # def send_to_jail
- # get '/jail'
- # assert_response :success
- # assert_template "jail/front"
- # end
- #
- # def goes_to_login
- # get login_url
- # #...
- # end
- #
- # == View a list of all your routes
- #
- # Run rake routes.
- #
- module Routing
- SEPARATORS = %w( / . ? )
-
- HTTP_METHODS = [:get, :head, :post, :put, :delete, :options]
-
- ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
-
- # The root paths which may contain controller files
- mattr_accessor :controller_paths
- self.controller_paths = []
-
- # A helper module to hold URL related helpers.
- module Helpers
- include PolymorphicRoutes
- end
-
- class << self
- # Expects an array of controller names as the first argument.
- # Executes the passed block with only the named controllers named available.
- # This method is used in internal Rails testing.
- def with_controllers(names)
- prior_controllers = @possible_controllers
- use_controllers! names
- yield
- ensure
- use_controllers! prior_controllers
- end
-
- # Returns an array of paths, cleaned of double-slashes and relative path references.
- # * "\\\" and "//" become "\\" or "/".
- # * "/foo/bar/../config" becomes "/foo/config".
- # The returned array is sorted by length, descending.
- def normalize_paths(paths)
- # do the hokey-pokey of path normalization...
- paths = paths.collect do |path|
- path = path.
- gsub("//", "/"). # replace double / chars with a single
- gsub("\\\\", "\\"). # replace double \ chars with a single
- gsub(%r{(.)[\\/]$}, '\1') # drop final / or \ if path ends with it
-
- # eliminate .. paths where possible
- re = %r{[^/\\]+[/\\]\.\.[/\\]}
- path.gsub!(re, "") while path.match(re)
- path
- end
-
- # start with longest path, first
- paths = paths.uniq.sort_by { |path| - path.length }
- end
-
- # Returns the array of controller names currently available to ActionController::Routing.
- def possible_controllers
- unless @possible_controllers
- @possible_controllers = []
-
- paths = controller_paths.select { |path| File.directory?(path) && path != "." }
-
- seen_paths = Hash.new {|h, k| h[k] = true; false}
- normalize_paths(paths).each do |load_path|
- Dir["#{load_path}/**/*_controller.rb"].collect do |path|
- next if seen_paths[path.gsub(%r{^\.[/\\]}, "")]
-
- controller_name = path[(load_path.length + 1)..-1]
-
- controller_name.gsub!(/_controller\.rb\Z/, '')
- @possible_controllers << controller_name
- end
- end
-
- # remove duplicates
- @possible_controllers.uniq!
- end
- @possible_controllers
- end
-
- # Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
- # ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
- def use_controllers!(controller_names)
- @possible_controllers = controller_names
- end
-
- # Returns a controller path for a new +controller+ based on a +previous+ controller path.
- # Handles 4 scenarios:
- #
- # * stay in the previous controller:
- # controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
- #
- # * stay in the previous namespace:
- # controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
- #
- # * forced move to the root namespace:
- # controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
- #
- # * previous namespace is root:
- # controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
- #
- def controller_relative_to(controller, previous)
- if controller.nil? then previous
- elsif controller[0] == ?/ then controller[1..-1]
- elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
- else controller
- end
- end
- end
-
- Routes = RouteSet.new
-
- ActiveSupport::Inflector.module_eval do
- # Ensures that routes are reloaded when Rails inflections are updated.
- def inflections_with_route_reloading(&block)
- returning(inflections_without_route_reloading(&block)) {
- ActionController::Routing::Routes.reload! if block_given?
- }
- end
-
- alias_method_chain :inflections, :route_reloading
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/polymorphic_routes.rb b/actionpack/lib/action_controller/routing/polymorphic_routes.rb
deleted file mode 100644
index 2adf3575a7..0000000000
--- a/actionpack/lib/action_controller/routing/polymorphic_routes.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-module ActionController
- # Polymorphic URL helpers are methods for smart resolution to a named route call when
- # given an Active Record model instance. They are to be used in combination with
- # ActionController::Resources.
- #
- # These methods are useful when you want to generate correct URL or path to a RESTful
- # resource without having to know the exact type of the record in question.
- #
- # Nested resources and/or namespaces are also supported, as illustrated in the example:
- #
- # polymorphic_url([:admin, @article, @comment])
- #
- # results in:
- #
- # admin_article_comment_url(@article, @comment)
- #
- # == Usage within the framework
- #
- # Polymorphic URL helpers are used in a number of places throughout the Rails framework:
- #
- # * url_for, so you can use it with a record as the argument, e.g.
- # url_for(@article);
- # * ActionView::Helpers::FormHelper uses polymorphic_path, so you can write
- # form_for(@article) without having to specify :url parameter for the form
- # action;
- # * redirect_to (which, in fact, uses url_for) so you can write
- # redirect_to(post) in your controllers;
- # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
- # for feed entries.
- #
- # == Prefixed polymorphic helpers
- #
- # In addition to polymorphic_url and polymorphic_path methods, a
- # number of prefixed helpers are available as a shorthand to :action => "..."
- # in options. Those are:
- #
- # * edit_polymorphic_url, edit_polymorphic_path
- # * new_polymorphic_url, new_polymorphic_path
- #
- # Example usage:
- #
- # edit_polymorphic_path(@post) # => "/posts/1/edit"
- # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
- module PolymorphicRoutes
- # Constructs a call to a named RESTful route for the given record and returns the
- # resulting URL string. For example:
- #
- # # calls post_url(post)
- # polymorphic_url(post) # => "http://example.com/posts/1"
- # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
- # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
- # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
- # polymorphic_url(Comment) # => "http://example.com/comments"
- #
- # ==== Options
- #
- # * :action - Specifies the action prefix for the named route:
- # :new or :edit. Default is no prefix.
- # * :routing_type - Allowed values are :path or :url.
- # Default is :url.
- #
- # ==== Examples
- #
- # # an Article record
- # polymorphic_url(record) # same as article_url(record)
- #
- # # a Comment record
- # polymorphic_url(record) # same as comment_url(record)
- #
- # # it recognizes new records and maps to the collection
- # record = Comment.new
- # polymorphic_url(record) # same as comments_url()
- #
- # # the class of a record will also map to the collection
- # polymorphic_url(Comment) # same as comments_url()
- #
- def polymorphic_url(record_or_hash_or_array, options = {})
- if record_or_hash_or_array.kind_of?(Array)
- record_or_hash_or_array = record_or_hash_or_array.compact
- record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
- end
-
- record = extract_record(record_or_hash_or_array)
- record = record.to_model if record.respond_to?(:to_model)
- namespace = extract_namespace(record_or_hash_or_array)
-
- args = case record_or_hash_or_array
- when Hash; [ record_or_hash_or_array ]
- when Array; record_or_hash_or_array.dup
- else [ record_or_hash_or_array ]
- end
-
- inflection = if options[:action].to_s == "new"
- args.pop
- :singular
- elsif (record.respond_to?(:new_record?) && record.new_record?) ||
- (record.respond_to?(:destroyed?) && record.destroyed?)
- args.pop
- :plural
- elsif record.is_a?(Class)
- args.pop
- :plural
- else
- :singular
- end
-
- args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
-
- named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
-
- url_options = options.except(:action, :routing_type)
- unless url_options.empty?
- args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
- end
-
- __send__(named_route, *args)
- end
-
- # Returns the path component of a URL for the given record. It uses
- # polymorphic_url with :routing_type => :path.
- def polymorphic_path(record_or_hash_or_array, options = {})
- polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
- end
-
- %w(edit new).each do |action|
- module_eval <<-EOT, __FILE__, __LINE__
- def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
- end # end
- #
- def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
- end # end
- EOT
- end
-
- def formatted_polymorphic_url(record_or_hash, options = {})
- ActiveSupport::Deprecation.warn("formatted_polymorphic_url has been deprecated. Please pass :format to the polymorphic_url method instead", caller)
- options[:format] = record_or_hash.pop if Array === record_or_hash
- polymorphic_url(record_or_hash, options)
- end
-
- def formatted_polymorphic_path(record_or_hash, options = {})
- ActiveSupport::Deprecation.warn("formatted_polymorphic_path has been deprecated. Please pass :format to the polymorphic_path method instead", caller)
- options[:format] = record_or_hash.pop if record_or_hash === Array
- polymorphic_url(record_or_hash, options.merge(:routing_type => :path))
- end
-
- private
- def action_prefix(options)
- options[:action] ? "#{options[:action]}_" : ''
- end
-
- def routing_type(options)
- options[:routing_type] || :url
- end
-
- def build_named_route_call(records, namespace, inflection, options = {})
- unless records.is_a?(Array)
- record = extract_record(records)
- route = ''
- else
- record = records.pop
- route = records.inject("") do |string, parent|
- if parent.is_a?(Symbol) || parent.is_a?(String)
- string << "#{parent}_"
- else
- string << "#{RecordIdentifier.__send__("plural_class_name", parent)}".singularize
- string << "_"
- end
- end
- end
-
- if record.is_a?(Symbol) || record.is_a?(String)
- route << "#{record}_"
- else
- route << "#{RecordIdentifier.__send__("plural_class_name", record)}"
- route = route.singularize if inflection == :singular
- route << "_"
- end
-
- action_prefix(options) + namespace + route + routing_type(options).to_s
- end
-
- def extract_record(record_or_hash_or_array)
- case record_or_hash_or_array
- when Array; record_or_hash_or_array.last
- when Hash; record_or_hash_or_array[:id]
- else record_or_hash_or_array
- end
- end
-
- # Remove the first symbols from the array and return the url prefix
- # implied by those symbols.
- def extract_namespace(record_or_hash_or_array)
- return "" unless record_or_hash_or_array.is_a?(Array)
-
- namespace_keys = []
- while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol)
- namespace_keys << record_or_hash_or_array.shift
- end
-
- namespace_keys.map {|k| "#{k}_"}.join
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb
deleted file mode 100644
index 06506435a2..0000000000
--- a/actionpack/lib/action_controller/routing/resources.rb
+++ /dev/null
@@ -1,685 +0,0 @@
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/object/try'
-
-module ActionController
- # == Overview
- #
- # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
- # is something that can be pointed at and it will respond with a representation of the data requested.
- # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
- # requests XML data.
- #
- # RESTful design is based on the assumption that there are four generic verbs that a user of an
- # application can request from a \resource (the noun).
- #
- # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
- # denotes the type of action that should take place.
- #
- # === The Different Methods and their Usage
- #
- # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
- # * POST - Creation of \resources.
- # * PUT - Editing of attributes on a \resource.
- # * DELETE - Deletion of a \resource.
- #
- # === Examples
- #
- # # A GET request on the Posts resource is asking for all Posts
- # GET /posts
- #
- # # A GET request on a single Post resource is asking for that particular Post
- # GET /posts/1
- #
- # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
- # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
- #
- # # A PUT request on a single Post resource is asking for a Post to be updated
- # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
- #
- # # A DELETE request on a single Post resource is asking for it to be deleted
- # DELETE /posts # with => { :id => 1 }
- #
- # By using the REST convention, users of our application can assume certain things about how the data
- # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
- # supplying you with methods to create them in your routes.rb file.
- #
- # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
- module Resources
- INHERITABLE_OPTIONS = :namespace, :shallow
-
- class Resource #:nodoc:
- DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
-
- attr_reader :collection_methods, :member_methods, :new_methods
- attr_reader :path_prefix, :name_prefix, :path_segment
- attr_reader :plural, :singular
- attr_reader :options
-
- def initialize(entities, options)
- @plural ||= entities
- @singular ||= options[:singular] || plural.to_s.singularize
- @path_segment = options.delete(:as) || @plural
-
- @options = options
-
- arrange_actions
- add_default_actions
- set_allowed_actions
- set_prefixes
- end
-
- def controller
- @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
- end
-
- def requirements(with_id = false)
- @requirements ||= @options[:requirements] || {}
- @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
-
- with_id ? @requirements.merge(@id_requirement) : @requirements
- end
-
- def conditions
- @conditions ||= @options[:conditions] || {}
- end
-
- def path
- @path ||= "#{path_prefix}/#{path_segment}"
- end
-
- def new_path
- new_action = self.options[:path_names][:new] if self.options[:path_names]
- new_action ||= Base.resources_path_names[:new]
- @new_path ||= "#{path}/#{new_action}"
- end
-
- def shallow_path_prefix
- @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
- end
-
- def member_path
- @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
- end
-
- def nesting_path_prefix
- @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
- end
-
- def shallow_name_prefix
- @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
- end
-
- def nesting_name_prefix
- "#{shallow_name_prefix}#{singular}_"
- end
-
- def action_separator
- @action_separator ||= Base.resource_action_separator
- end
-
- def uncountable?
- @singular.to_s == @plural.to_s
- end
-
- def has_action?(action)
- !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
- end
-
- protected
- def arrange_actions
- @collection_methods = arrange_actions_by_methods(options.delete(:collection))
- @member_methods = arrange_actions_by_methods(options.delete(:member))
- @new_methods = arrange_actions_by_methods(options.delete(:new))
- end
-
- def add_default_actions
- add_default_action(member_methods, :get, :edit)
- add_default_action(new_methods, :get, :new)
- end
-
- def set_allowed_actions
- only, except = @options.values_at(:only, :except)
- @allowed_actions ||= {}
-
- if only == :all || except == :none
- only = nil
- except = []
- elsif only == :none || except == :all
- only = []
- except = nil
- end
-
- if only
- @allowed_actions[:only] = Array(only).map {|a| a.to_sym }
- elsif except
- @allowed_actions[:except] = Array(except).map {|a| a.to_sym }
- end
- end
-
- def action_allowed?(action)
- only, except = @allowed_actions.values_at(:only, :except)
- (!only || only.include?(action)) && (!except || !except.include?(action))
- end
-
- def set_prefixes
- @path_prefix = options.delete(:path_prefix)
- @name_prefix = options.delete(:name_prefix)
- end
-
- def arrange_actions_by_methods(actions)
- (actions || {}).inject({}) do |flipped_hash, (key, value)|
- (flipped_hash[value] ||= []) << key
- flipped_hash
- end
- end
-
- def add_default_action(collection, method, action)
- (collection[method] ||= []).unshift(action)
- end
- end
-
- class SingletonResource < Resource #:nodoc:
- def initialize(entity, options)
- @singular = @plural = entity
- options[:controller] ||= @singular.to_s.pluralize
- super
- end
-
- alias_method :shallow_path_prefix, :path_prefix
- alias_method :shallow_name_prefix, :name_prefix
- alias_method :member_path, :path
- alias_method :nesting_path_prefix, :path
- end
-
- # Creates named routes for implementing verb-oriented controllers
- # for a collection \resource.
- #
- # For example:
- #
- # map.resources :messages
- #
- # will map the following actions in the corresponding controller:
- #
- # class MessagesController < ActionController::Base
- # # GET messages_url
- # def index
- # # return all messages
- # end
- #
- # # GET new_message_url
- # def new
- # # return an HTML form for describing a new message
- # end
- #
- # # POST messages_url
- # def create
- # # create a new message
- # end
- #
- # # GET message_url(:id => 1)
- # def show
- # # find and return a specific message
- # end
- #
- # # GET edit_message_url(:id => 1)
- # def edit
- # # return an HTML form for editing a specific message
- # end
- #
- # # PUT message_url(:id => 1)
- # def update
- # # find and update a specific message
- # end
- #
- # # DELETE message_url(:id => 1)
- # def destroy
- # # delete a specific message
- # end
- # end
- #
- # Along with the routes themselves, +resources+ generates named routes for use in
- # controllers and views. map.resources :messages produces the following named routes and helpers:
- #
- # Named Route Helpers
- # ============ =====================================================
- # messages messages_url, hash_for_messages_url,
- # messages_path, hash_for_messages_path
- #
- # message message_url(id), hash_for_message_url(id),
- # message_path(id), hash_for_message_path(id)
- #
- # new_message new_message_url, hash_for_new_message_url,
- # new_message_path, hash_for_new_message_path
- #
- # edit_message edit_message_url(id), hash_for_edit_message_url(id),
- # edit_message_path(id), hash_for_edit_message_path(id)
- #
- # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
- #
- # redirect_to :controller => 'messages', :action => 'index'
- # # and
- # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
- #
- # now become:
- #
- # redirect_to messages_url
- # # and
- # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
- #
- # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
- # form tags. The form helpers make this a little easier. For an update form with a @message object:
- #
- # <%= form_tag message_path(@message), :method => :put %>
- #
- # or
- #
- # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
- #
- # or
- #
- # <% form_for @message do |f| %>
- #
- # which takes into account whether @message is a new record or not and generates the
- # path and method accordingly.
- #
- # The +resources+ method accepts the following options to customize the resulting routes:
- # * :collection - Add named routes for other actions that operate on the collection.
- # Takes a hash of #{action} => #{method}, where method is :get/:post/:put/:delete,
- # an array of any of the previous, or :any if the method does not matter.
- # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
- # * :member - Same as :collection, but for actions that operate on a specific member.
- # * :new - Same as :collection, but for actions that operate on the new \resource action.
- # * :controller - Specify the controller name for the routes.
- # * :singular - Specify the singular name used in the member routes.
- # * :requirements - Set custom routing parameter requirements; this is a hash of either
- # regular expressions (which must match for the route to match) or extra parameters. For example:
- #
- # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
- #
- # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
- # * :conditions - Specify custom routing recognition conditions. \Resources sets the :method value for the method-specific routes.
- # * :as - Specify a different \resource name to use in the URL path. For example:
- # # products_path == '/productos'
- # map.resources :products, :as => 'productos' do |product|
- # # product_reviews_path(product) == '/productos/1234/comentarios'
- # product.resources :product_reviews, :as => 'comentarios'
- # end
- #
- # * :has_one - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
- # * :has_many - Same has :has_one, but for plural \resources.
- #
- # You may directly specify the routing association with +has_one+ and +has_many+ like:
- #
- # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
- #
- # This is the same as:
- #
- # map.resources :notes do |notes|
- # notes.resource :author
- # notes.resources :comments
- # notes.resources :attachments
- # end
- #
- # * :path_names - Specify different path names for the actions. For example:
- # # new_products_path == '/productos/nuevo'
- # # bids_product_path(1) == '/productos/1/licitacoes'
- # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
- #
- # You can also set default action names from an environment, like this:
- # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
- #
- # * :path_prefix - Set a prefix to the routes with required route variables.
- #
- # Weblog comments usually belong to a post, so you might use +resources+ like:
- #
- # map.resources :articles
- # map.resources :comments, :path_prefix => '/articles/:article_id'
- #
- # You can nest +resources+ calls to set this automatically:
- #
- # map.resources :articles do |article|
- # article.resources :comments
- # end
- #
- # The comment \resources work the same, but must now include a value for :article_id.
- #
- # article_comments_url(@article)
- # article_comment_url(@article, @comment)
- #
- # article_comments_url(:article_id => @article)
- # article_comment_url(:article_id => @article, :id => @comment)
- #
- # If you don't want to load all objects from the database you might want to use the article_id directly:
- #
- # articles_comments_url(@comment.article_id, @comment)
- #
- # * :name_prefix - Define a prefix for all generated routes, usually ending in an underscore.
- # Use this if you have named routes that may clash.
- #
- # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
- # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
- #
- # You may also use :name_prefix to override the generic named routes in a nested \resource:
- #
- # map.resources :articles do |article|
- # article.resources :comments, :name_prefix => nil
- # end
- #
- # This will yield named \resources like so:
- #
- # comments_url(@article)
- # comment_url(@article, @comment)
- #
- # * :shallow - If true, paths for nested resources which reference a specific member
- # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
- #
- # The :shallow option is inherited by any nested resource(s).
- #
- # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
- #
- # map.resources :users, :shallow => true do |user|
- # user.resources :posts do |post|
- # post.resources :comments
- # end
- # end
- # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
- # # also adds the usual named route called "user_posts"
- # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
- # # also adds the named route called "post"
- # # --> GET /posts/2/comments (maps to the CommentsController#index action)
- # # also adds the named route called "post_comments"
- # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
- # # also adds the named route called "comment"
- #
- # You may also use :shallow in combination with the +has_one+ and +has_many+ shorthand notations like:
- #
- # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
- #
- # * :only and :except - Specify which of the seven default actions should be routed to.
- #
- # :only and :except may be set to :all, :none, an action name or a
- # list of action names. By default, routes are generated for all seven actions.
- #
- # For example:
- #
- # map.resources :posts, :only => [:index, :show] do |post|
- # post.resources :comments, :except => [:update, :destroy]
- # end
- # # --> GET /posts (maps to the PostsController#index action)
- # # --> POST /posts (fails)
- # # --> GET /posts/1 (maps to the PostsController#show action)
- # # --> DELETE /posts/1 (fails)
- # # --> POST /posts/1/comments (maps to the CommentsController#create action)
- # # --> PUT /posts/1/comments/1 (fails)
- #
- # If map.resources is called with multiple resources, they all get the same options applied.
- #
- # Examples:
- #
- # map.resources :messages, :path_prefix => "/thread/:thread_id"
- # # --> GET /thread/7/messages/1
- #
- # map.resources :messages, :collection => { :rss => :get }
- # # --> GET /messages/rss (maps to the #rss action)
- # # also adds a named route called "rss_messages"
- #
- # map.resources :messages, :member => { :mark => :post }
- # # --> POST /messages/1/mark (maps to the #mark action)
- # # also adds a named route called "mark_message"
- #
- # map.resources :messages, :new => { :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- #
- # map.resources :messages, :new => { :new => :any, :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- # # --> /messages/new can be invoked via any request method
- #
- # map.resources :messages, :controller => "categories",
- # :path_prefix => "/category/:category_id",
- # :name_prefix => "category_"
- # # --> GET /categories/7/messages/1
- # # has named route "category_message"
- #
- # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
- # HTTP POST on new_message_url will raise a RoutingError exception. The default route in
- # config/routes.rb overrides this and allows invalid HTTP methods for \resource routes.
- def resources(*entities, &block)
- options = entities.extract_options!
- entities.each { |entity| map_resource(entity, options.dup, &block) }
- end
-
- # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
- # A singleton \resource is global to its current context. For unnested singleton \resources,
- # the \resource is global to the current user visiting the application, such as a user's
- # /account profile. For nested singleton \resources, the \resource is global to its parent
- # \resource, such as a projects \resource that has_one :project_manager.
- # The project_manager should be mapped as a singleton \resource under projects:
- #
- # map.resources :projects do |project|
- # project.resource :project_manager
- # end
- #
- # See +resources+ for general conventions. These are the main differences:
- # * A singular name is given to map.resource. The default controller name is still taken from the plural name.
- # * To specify a custom plural name, use the :plural option. There is no :singular option.
- # * No default index route is created for the singleton \resource controller.
- # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
- #
- # For example:
- #
- # map.resource :account
- #
- # maps these actions in the Accounts controller:
- #
- # class AccountsController < ActionController::Base
- # # GET new_account_url
- # def new
- # # return an HTML form for describing the new account
- # end
- #
- # # POST account_url
- # def create
- # # create an account
- # end
- #
- # # GET account_url
- # def show
- # # find and return the account
- # end
- #
- # # GET edit_account_url
- # def edit
- # # return an HTML form for editing the account
- # end
- #
- # # PUT account_url
- # def update
- # # find and update the account
- # end
- #
- # # DELETE account_url
- # def destroy
- # # delete the account
- # end
- # end
- #
- # Along with the routes themselves, +resource+ generates named routes for
- # use in controllers and views. map.resource :account produces
- # these named routes and helpers:
- #
- # Named Route Helpers
- # ============ =============================================
- # account account_url, hash_for_account_url,
- # account_path, hash_for_account_path
- #
- # new_account new_account_url, hash_for_new_account_url,
- # new_account_path, hash_for_new_account_path
- #
- # edit_account edit_account_url, hash_for_edit_account_url,
- # edit_account_path, hash_for_edit_account_path
- def resource(*entities, &block)
- options = entities.extract_options!
- entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
- end
-
- private
- def map_resource(entities, options = {}, &block)
- resource = Resource.new(entities, options)
-
- with_options :controller => resource.controller do |map|
- map_associations(resource, options)
-
- if block_given?
- with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
- end
-
- map_collection_actions(map, resource)
- map_default_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
- end
- end
-
- def map_singleton_resource(entities, options = {}, &block)
- resource = SingletonResource.new(entities, options)
-
- with_options :controller => resource.controller do |map|
- map_associations(resource, options)
-
- if block_given?
- with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
- end
-
- map_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
- map_default_singleton_actions(map, resource)
- end
- end
-
- def map_associations(resource, options)
- map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
-
- path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
- name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
-
- Array(options[:has_one]).each do |association|
- resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
- end
- end
-
- def map_has_many_associations(resource, associations, options)
- case associations
- when Hash
- associations.each do |association,has_many|
- map_has_many_associations(resource, association, options.merge(:has_many => has_many))
- end
- when Array
- associations.each do |association|
- map_has_many_associations(resource, association, options)
- end
- when Symbol, String
- resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
- else
- end
- end
-
- def map_collection_actions(map, resource)
- resource.collection_methods.each do |method, actions|
- actions.each do |action|
- [method].flatten.each do |m|
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
- action_path ||= action
-
- map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
- end
- end
- end
- end
-
- def map_default_collection_actions(map, resource)
- index_route_name = "#{resource.name_prefix}#{resource.plural}"
-
- if resource.uncountable?
- index_route_name << "_index"
- end
-
- map_resource_routes(map, resource, :index, resource.path, index_route_name)
- map_resource_routes(map, resource, :create, resource.path, index_route_name)
- end
-
- def map_default_singleton_actions(map, resource)
- map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
- end
-
- def map_new_actions(map, resource)
- resource.new_methods.each do |method, actions|
- actions.each do |action|
- route_path = resource.new_path
- route_name = "new_#{resource.name_prefix}#{resource.singular}"
-
- unless action == :new
- route_path = "#{route_path}#{resource.action_separator}#{action}"
- route_name = "#{action}_#{route_name}"
- end
-
- map_resource_routes(map, resource, action, route_path, route_name, method)
- end
- end
- end
-
- def map_member_actions(map, resource)
- resource.member_methods.each do |method, actions|
- actions.each do |action|
- [method].flatten.each do |m|
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
- action_path ||= Base.resources_path_names[action] || action
-
- map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
- end
- end
- end
-
- route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
- map_resource_routes(map, resource, :show, resource.member_path, route_path)
- map_resource_routes(map, resource, :update, resource.member_path, route_path)
- map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
- end
-
- def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
- if resource.has_action?(action)
- action_options = action_options_for(action, resource, method, resource_options)
- formatted_route_path = "#{route_path}.:format"
-
- if route_name && @set.named_routes[route_name.to_sym].nil?
- map.named_route(route_name, formatted_route_path, action_options)
- else
- map.connect(formatted_route_path, action_options)
- end
- end
- end
-
- def add_conditions_for(conditions, method)
- returning({:conditions => conditions.dup}) do |options|
- options[:conditions][:method] = method unless method == :any
- end
- end
-
- def action_options_for(action, resource, method = nil, resource_options = {})
- default_options = { :action => action.to_s }
- require_id = !resource.kind_of?(SingletonResource)
- force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
-
- case default_options[:action]
- when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
- when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
- when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
- when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
- when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
- else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
deleted file mode 100644
index 8135b5811e..0000000000
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ /dev/null
@@ -1,699 +0,0 @@
-require 'rack/mount'
-require 'forwardable'
-
-module ActionController
- module Routing
- class RouteSet #:nodoc:
- NotFound = lambda { |env|
- raise RoutingError, "No route matches #{env[::Rack::Mount::Const::PATH_INFO].inspect} with #{env.inspect}"
- }
-
- PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
-
- class Dispatcher
- def initialize(options = {})
- defaults = options[:defaults]
- @glob_param = options.delete(:glob)
- end
-
- def call(env)
- params = env[PARAMETERS_KEY]
- merge_default_action!(params)
- split_glob_param!(params) if @glob_param
- params.each { |key, value| params[key] = URI.unescape(value) if value.is_a?(String) }
-
- if env['action_controller.recognize']
- [200, {}, params]
- else
- controller = controller(params)
- controller.action(params[:action]).call(env)
- end
- end
-
- private
- def controller(params)
- if params && params.has_key?(:controller)
- controller = "#{params[:controller].camelize}Controller"
- ActiveSupport::Inflector.constantize(controller)
- end
- end
-
- def merge_default_action!(params)
- params[:action] ||= 'index'
- end
-
- def split_glob_param!(params)
- params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) }
- end
- end
-
- module RouteExtensions
- def segment_keys
- conditions[:path_info].names.compact.map { |key| key.to_sym }
- end
- end
-
- # Mapper instances are used to build routes. The object passed to the draw
- # block in config/routes.rb is a Mapper instance.
- #
- # Mapper instances have relatively few instance methods, in order to avoid
- # clashes with named routes.
- class Mapper #:doc:
- include ActionController::Resources
-
- def initialize(set) #:nodoc:
- @set = set
- end
-
- # Create an unnamed route with the provided +path+ and +options+. See
- # ActionController::Routing for an introduction to routes.
- def connect(path, options = {})
- @set.add_route(path, options)
- end
-
- # Creates a named route called "root" for matching the root level request.
- def root(options = {})
- if options.is_a?(Symbol)
- if source_route = @set.named_routes.routes[options]
- options = source_route.defaults.merge({ :conditions => source_route.conditions })
- end
- end
- named_route("root", '', options)
- end
-
- def named_route(name, path, options = {}) #:nodoc:
- @set.add_named_route(name, path, options)
- end
-
- # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
- # Example:
- #
- # map.namespace(:admin) do |admin|
- # admin.resources :products,
- # :has_many => [ :tags, :images, :variants ]
- # end
- #
- # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
- # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
- # Admin::TagsController.
- def namespace(name, options = {}, &block)
- if options[:namespace]
- with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
- else
- with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
- end
- end
-
- def method_missing(route_name, *args, &proc) #:nodoc:
- super unless args.length >= 1 && proc.nil?
- @set.add_named_route(route_name, *args)
- end
- end
-
- # A NamedRouteCollection instance is a collection of named routes, and also
- # maintains an anonymous module that can be used to install helpers for the
- # named routes.
- class NamedRouteCollection #:nodoc:
- include Enumerable
- attr_reader :routes, :helpers
-
- def initialize
- clear!
- end
-
- def clear!
- @routes = {}
- @helpers = []
-
- @module ||= Module.new
- @module.instance_methods.each do |selector|
- @module.class_eval { remove_method selector }
- end
- end
-
- def add(name, route)
- routes[name.to_sym] = route
- define_named_route_methods(name, route)
- end
-
- def get(name)
- routes[name.to_sym]
- end
-
- alias []= add
- alias [] get
- alias clear clear!
-
- def each
- routes.each { |name, route| yield name, route }
- self
- end
-
- def names
- routes.keys
- end
-
- def length
- routes.length
- end
-
- def reset!
- old_routes = routes.dup
- clear!
- old_routes.each do |name, route|
- add(name, route)
- end
- end
-
- def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
- reset! if regenerate
- Array(destinations).each do |dest|
- dest.__send__(:include, @module)
- end
- end
-
- private
- def url_helper_name(name, kind = :url)
- :"#{name}_#{kind}"
- end
-
- def hash_access_name(name, kind = :url)
- :"hash_for_#{name}_#{kind}"
- end
-
- def define_named_route_methods(name, route)
- {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
- hash = route.defaults.merge(:use_route => name).merge(opts)
- define_hash_access route, name, kind, hash
- define_url_helper route, name, kind, hash
- end
- end
-
- def named_helper_module_eval(code, *args)
- @module.module_eval(code, *args)
- end
-
- def define_hash_access(route, name, kind, options)
- selector = hash_access_name(name, kind)
- named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
- def #{selector}(options = nil) # def hash_for_users_url(options = nil)
- options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false}
- end # end
- protected :#{selector} # protected :hash_for_users_url
- end_eval
- helpers << selector
- end
-
- def define_url_helper(route, name, kind, options)
- selector = url_helper_name(name, kind)
- # The segment keys used for positional parameters
-
- hash_access_method = hash_access_name(name, kind)
-
- # allow ordered parameters to be associated with corresponding
- # dynamic segments, so you can do
- #
- # foo_url(bar, baz, bang)
- #
- # instead of
- #
- # foo_url(:bar => bar, :baz => baz, :bang => bang)
- #
- # Also allow options hash, so you can do
- #
- # foo_url(bar, baz, bang, :sort_by => 'baz')
- #
- named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
- def #{selector}(*args) # def users_url(*args)
- #
- opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
- args.first || {} # args.first || {}
- else # else
- options = args.extract_options! # options = args.extract_options!
- args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
- h[k] = v # h[k] = v
- h # h
- end # end
- options.merge(args) # options.merge(args)
- end # end
- #
- url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
- #
- end # end
- #Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL.
- def formatted_#{selector}(*args) # def formatted_users_url(*args)
- ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
- "formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
- "Please pass format to the standard " + # "Please pass format to the standard " +
- "#{selector} method instead.", caller) # "users_url method instead.", caller)
- #{selector}(*args) # users_url(*args)
- end # end
- protected :#{selector} # protected :users_url
- end_eval
- helpers << selector
- end
- end
-
- attr_accessor :routes, :named_routes, :configuration_files
-
- def initialize
- self.configuration_files = []
-
- self.routes = []
- self.named_routes = NamedRouteCollection.new
-
- clear!
- end
-
- def draw
- clear!
- yield Mapper.new(self)
- @set.add_route(NotFound)
- install_helpers
- @set.freeze
- end
-
- def clear!
- routes.clear
- named_routes.clear
- @set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY)
- end
-
- def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
- Array(destinations).each { |d| d.module_eval { include Helpers } }
- named_routes.install(destinations, regenerate_code)
- end
-
- def empty?
- routes.empty?
- end
-
- def add_configuration_file(path)
- self.configuration_files << path
- end
-
- # Deprecated accessor
- def configuration_file=(path)
- add_configuration_file(path)
- end
-
- # Deprecated accessor
- def configuration_file
- configuration_files
- end
-
- def load!
- Routing.use_controllers!(nil) # Clear the controller cache so we may discover new ones
- load_routes!
- end
-
- # reload! will always force a reload whereas load checks the timestamp first
- alias reload! load!
-
- def reload
- if configuration_files.any? && @routes_last_modified
- if routes_changed_at == @routes_last_modified
- return # routes didn't change, don't reload
- else
- @routes_last_modified = routes_changed_at
- end
- end
-
- load!
- end
-
- def load_routes!
- if configuration_files.any?
- configuration_files.each { |config| load(config) }
- @routes_last_modified = routes_changed_at
- else
- draw do |map|
- map.connect ":controller/:action/:id"
- end
- end
- end
-
- def routes_changed_at
- routes_changed_at = nil
-
- configuration_files.each do |config|
- config_changed_at = File.stat(config).mtime
-
- if routes_changed_at.nil? || config_changed_at > routes_changed_at
- routes_changed_at = config_changed_at
- end
- end
-
- routes_changed_at
- end
-
- def add_route(path, options = {})
- options = options.dup
-
- if conditions = options.delete(:conditions)
- conditions = conditions.dup
- method = [conditions.delete(:method)].flatten.compact
- method.map! { |m|
- m = m.to_s.upcase
-
- if m == "HEAD"
- raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
- end
-
- unless HTTP_METHODS.include?(m.downcase.to_sym)
- raise ArgumentError, "Invalid HTTP method specified in route conditions"
- end
-
- m
- }
-
- if method.length > 1
- method = Regexp.union(*method)
- elsif method.length == 1
- method = method.first
- else
- method = nil
- end
- end
-
- path_prefix = options.delete(:path_prefix)
- name_prefix = options.delete(:name_prefix)
- namespace = options.delete(:namespace)
-
- name = options.delete(:_name)
- name = "#{name_prefix}#{name}" if name_prefix
-
- requirements = options.delete(:requirements) || {}
- defaults = options.delete(:defaults) || {}
- options.each do |k, v|
- if v.is_a?(Regexp)
- if value = options.delete(k)
- requirements[k.to_sym] = value
- end
- else
- value = options.delete(k)
- defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
- end
- end
-
- requirements.each do |_, requirement|
- if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
- end
- if requirement.multiline?
- raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
- end
- end
-
- possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
- requirements[:controller] ||= Regexp.union(*possible_names)
-
- if defaults[:controller]
- defaults[:action] ||= 'index'
- defaults[:controller] = defaults[:controller].to_s
- defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
- end
-
- if defaults[:action]
- defaults[:action] = defaults[:action].to_s
- end
-
- if path.is_a?(String)
- path = "#{path_prefix}/#{path}" if path_prefix
- path = path.gsub('.:format', '(.:format)')
- path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
- glob = $1.to_sym if path =~ /\/\*(\w+)$/
- path = ::Rack::Mount::Utils.normalize_path(path)
- path = ::Rack::Mount::Strexp.compile(path, requirements, %w( / . ? ))
-
- if glob && !defaults[glob].blank?
- raise RoutingError, "paths cannot have non-empty default values"
- end
- end
-
- app = Dispatcher.new(:defaults => defaults, :glob => glob)
-
- conditions = {}
- conditions[:request_method] = method if method
- conditions[:path_info] = path if path
-
- route = @set.add_route(app, conditions, defaults, name)
- route.extend(RouteExtensions)
- routes << route
- route
- end
-
- def add_named_route(name, path, options = {})
- options[:_name] = name
- route = add_route(path, options)
- named_routes[route.name] = route
- route
- end
-
- def options_as_params(options)
- # If an explicit :controller was given, always make :action explicit
- # too, so that action expiry works as expected for things like
- #
- # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
- #
- # (the above is from the unit tests). In the above case, because the
- # controller was explicitly given, but no action, the action is implied to
- # be "index", not the recalled action of "show".
- #
- # great fun, eh?
-
- options_as_params = options.clone
- options_as_params[:action] ||= 'index' if options[:controller]
- options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
- options_as_params
- end
-
- def build_expiry(options, recall)
- recall.inject({}) do |expiry, (key, recalled_value)|
- expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
- expiry
- end
- end
-
- # Generate the path indicated by the arguments, and return an array of
- # the keys that were not used to generate it.
- def extra_keys(options, recall={})
- generate_extras(options, recall).last
- end
-
- def generate_extras(options, recall={})
- generate(options, recall, :generate_extras)
- end
-
- def generate(options, recall = {}, method = :generate)
- options, recall = options.dup, recall.dup
- named_route = options.delete(:use_route)
-
- options = options_as_params(options)
- expire_on = build_expiry(options, recall)
-
- recall[:action] ||= 'index' if options[:controller] || recall[:controller]
-
- if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller])
- options[:controller] = recall.delete(:controller)
-
- if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action])
- options[:action] = recall.delete(:action)
-
- if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id])
- options[:id] = recall.delete(:id)
- end
- end
- end
-
- options[:controller] = options[:controller].to_s if options[:controller]
-
- if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
- old_parts = recall[:controller].split('/')
- new_parts = options[:controller].split('/')
- parts = old_parts[0..-(new_parts.length + 1)] + new_parts
- options[:controller] = parts.join('/')
- end
-
- options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
-
- merged = options.merge(recall)
- if options.has_key?(:action) && options[:action].nil?
- options.delete(:action)
- recall[:action] = 'index'
- end
- recall[:action] = options.delete(:action) if options[:action] == 'index'
-
- path = _uri(named_route, options, recall)
- if path && method == :generate_extras
- uri = URI(path)
- extras = uri.query ?
- Rack::Utils.parse_nested_query(uri.query).keys.map { |k| k.to_sym } :
- []
- [uri.path, extras]
- elsif path
- path
- else
- raise RoutingError, "No route matches #{options.inspect}"
- end
- rescue Rack::Mount::RoutingError
- raise RoutingError, "No route matches #{options.inspect}"
- end
-
- def call(env)
- @set.call(env)
- rescue ActionController::RoutingError => e
- raise e if env['action_controller.rescue_error'] == false
-
- method, path = env['REQUEST_METHOD'].downcase.to_sym, env['PATH_INFO']
-
- # Route was not recognized. Try to find out why (maybe wrong verb).
- allows = HTTP_METHODS.select { |verb|
- begin
- recognize_path(path, {:method => verb}, false)
- rescue ActionController::RoutingError
- nil
- end
- }
-
- if !HTTP_METHODS.include?(method)
- raise NotImplemented.new(*allows)
- elsif !allows.empty?
- raise MethodNotAllowed.new(*allows)
- else
- raise e
- end
- end
-
- def recognize(request)
- params = recognize_path(request.path, extract_request_environment(request))
- request.path_parameters = params.with_indifferent_access
- "#{params[:controller].to_s.camelize}Controller".constantize
- end
-
- def recognize_path(path, environment = {}, rescue_error = true)
- method = (environment[:method] || "GET").to_s.upcase
-
- begin
- env = Rack::MockRequest.env_for(path, {:method => method})
- rescue URI::InvalidURIError => e
- raise RoutingError, e.message
- end
-
- env['action_controller.recognize'] = true
- env['action_controller.rescue_error'] = rescue_error
- status, headers, body = call(env)
- body
- end
-
- # Subclasses and plugins may override this method to extract further attributes
- # from the request, for use by route conditions and such.
- def extract_request_environment(request)
- { :method => request.method }
- end
-
- private
- def _uri(named_route, params, recall)
- params = URISegment.wrap_values(params)
- recall = URISegment.wrap_values(recall)
-
- unless result = @set.generate(:path_info, named_route, params, recall)
- return
- end
-
- uri, params = result
- params.each do |k, v|
- if v._value
- params[k] = v._value
- else
- params.delete(k)
- end
- end
-
- uri << "?#{Rack::Mount::Utils.build_nested_query(params)}" if uri && params.any?
- uri
- end
-
- class URISegment < Struct.new(:_value, :_escape)
- EXCLUDED = [:controller]
-
- def self.wrap_values(hash)
- hash.inject({}) { |h, (k, v)|
- h[k] = new(v, !EXCLUDED.include?(k.to_sym))
- h
- }
- end
-
- extend Forwardable
- def_delegators :_value, :==, :eql?, :hash
-
- def to_param
- @to_param ||= begin
- if _value.is_a?(Array)
- _value.map { |v| _escaped(v) }.join('/')
- else
- _escaped(_value)
- end
- end
- end
- alias_method :to_s, :to_param
-
- private
- def _escaped(value)
- v = value.respond_to?(:to_param) ? value.to_param : value
- _escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s
- end
- end
-
- def optionalize_trailing_dynamic_segments(path, requirements, defaults)
- path = (path =~ /^\//) ? path.dup : "/#{path}"
- optional, segments = true, []
-
- required_segments = requirements.keys
- required_segments -= defaults.keys.compact
-
- old_segments = path.split('/')
- old_segments.shift
- length = old_segments.length
-
- old_segments.reverse.each_with_index do |segment, index|
- required_segments.each do |required|
- if segment =~ /#{required}/
- optional = false
- break
- end
- end
-
- if optional
- if segment == ":id" && segments.include?(":action")
- optional = false
- elsif segment == ":controller" || segment == ":action" || segment == ":id"
- # Ignore
- elsif !(segment =~ /^:\w+$/) &&
- !(segment =~ /^:\w+\(\.:format\)$/)
- optional = false
- elsif segment =~ /^:(\w+)$/
- if defaults.has_key?($1.to_sym)
- defaults.delete($1.to_sym)
- else
- optional = false
- end
- end
- end
-
- if optional && index < length - 1
- segments.unshift('(/', segment)
- segments.push(')')
- elsif optional
- segments.unshift('/(', segment)
- segments.push(')')
- else
- segments.unshift('/', segment)
- end
- end
-
- segments.join
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/url_rewriter.rb b/actionpack/lib/action_controller/routing/url_rewriter.rb
deleted file mode 100644
index 52b66c9303..0000000000
--- a/actionpack/lib/action_controller/routing/url_rewriter.rb
+++ /dev/null
@@ -1,204 +0,0 @@
-module ActionController
- # In routes.rb one defines URL-to-controller mappings, but the reverse
- # is also possible: an URL can be generated from one of your routing definitions.
- # URL generation functionality is centralized in this module.
- #
- # See ActionController::Routing and ActionController::Resources for general
- # information about routing and routes.rb.
- #
- # Tip: If you need to generate URLs from your models or some other place,
- # then ActionController::UrlWriter is what you're looking for. Read on for
- # an introduction.
- #
- # == URL generation from parameters
- #
- # As you may know, some functions - such as ActionController::Base#url_for
- # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
- # of parameters. For example, you've probably had the chance to write code
- # like this in one of your views:
- #
- # <%= link_to('Click here', :controller => 'users',
- # :action => 'new', :message => 'Welcome!') %>
- #
- # #=> Generates a link to: /users/new?message=Welcome%21
- #
- # link_to, and all other functions that require URL generation functionality,
- # actually use ActionController::UrlWriter under the hood. And in particular,
- # they use the ActionController::UrlWriter#url_for method. One can generate
- # the same path as the above example by using the following code:
- #
- # include UrlWriter
- # url_for(:controller => 'users',
- # :action => 'new',
- # :message => 'Welcome!',
- # :only_path => true)
- # # => "/users/new?message=Welcome%21"
- #
- # Notice the :only_path => true part. This is because UrlWriter has no
- # information about the website hostname that your Rails app is serving. So if you
- # want to include the hostname as well, then you must also pass the :host
- # argument:
- #
- # include UrlWriter
- # url_for(:controller => 'users',
- # :action => 'new',
- # :message => 'Welcome!',
- # :host => 'www.example.com') # Changed this.
- # # => "http://www.example.com/users/new?message=Welcome%21"
- #
- # By default, all controllers and views have access to a special version of url_for,
- # that already knows what the current hostname is. So if you use url_for in your
- # controllers or your views, then you don't need to explicitly pass the :host
- # argument.
- #
- # For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
- # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
- # in full. However, mailers don't have hostname information, and what's why you'll still
- # have to specify the :host argument when generating URLs in mailers.
- #
- #
- # == URL generation for named routes
- #
- # UrlWriter also allows one to access methods that have been auto-generated from
- # named routes. For example, suppose that you have a 'users' resource in your
- # routes.rb:
- #
- # map.resources :users
- #
- # This generates, among other things, the method users_path. By default,
- # this method is accessible from your controllers, views and mailers. If you need
- # to access this auto-generated method from other places (such as a model), then
- # you can do that by including ActionController::UrlWriter in your class:
- #
- # class User < ActiveRecord::Base
- # include ActionController::UrlWriter
- #
- # def base_uri
- # user_path(self)
- # end
- # end
- #
- # User.find(1).base_uri # => "/users/1"
- module UrlWriter
- def self.included(base) #:nodoc:
- ActionController::Routing::Routes.install_helpers(base)
- base.mattr_accessor :default_url_options
-
- # The default options for urls written by this writer. Typically a :host pair is provided.
- base.default_url_options ||= {}
- end
-
- # Generate a url based on the options provided, default_url_options and the
- # routes defined in routes.rb. The following options are supported:
- #
- # * :only_path - If true, the relative url is returned. Defaults to +false+.
- # * :protocol - The protocol to connect to. Defaults to 'http'.
- # * :host - Specifies the host the link should be targeted at.
- # If :only_path is false, this option must be
- # provided either explicitly, or via +default_url_options+.
- # * :port - Optionally specify the port to connect to.
- # * :anchor - An anchor name to be appended to the path.
- # * :skip_relative_url_root - If true, the url is not constructed using the
- # +relative_url_root+ set in ActionController::Base.relative_url_root.
- # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2009/"
- #
- # Any other key (:controller, :action, etc.) given to
- # +url_for+ is forwarded to the Routes module.
- #
- # Examples:
- #
- # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
- # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
- # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
- # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
- def url_for(options)
- options = self.class.default_url_options.merge(options)
-
- url = ''
-
- unless options.delete(:only_path)
- url << (options.delete(:protocol) || 'http')
- url << '://' unless url.match("://")
-
- raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
-
- url << options.delete(:host)
- url << ":#{options.delete(:port)}" if options.key?(:port)
- else
- # Delete the unused options to prevent their appearance in the query string.
- [:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
- end
- trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
- url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
- anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
- generated = Routing::Routes.generate(options, {})
- url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
- url << anchor if anchor
-
- url
- end
- end
-
- # Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
- class UrlRewriter #:nodoc:
- RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
- def initialize(request, parameters)
- @request, @parameters = request, parameters
- end
-
- def rewrite(options = {})
- rewrite_url(options)
- end
-
- def to_str
- "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}"
- end
-
- alias_method :to_s, :to_str
-
- private
- # Given a path and options, returns a rewritten URL string
- def rewrite_url(options)
- rewritten_url = ""
-
- unless options[:only_path]
- rewritten_url << (options[:protocol] || @request.protocol)
- rewritten_url << "://" unless rewritten_url.match("://")
- rewritten_url << rewrite_authentication(options)
- rewritten_url << (options[:host] || @request.host_with_port)
- rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
- end
-
- path = rewrite_path(options)
- rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
- rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
- rewritten_url << "##{CGI.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
-
- rewritten_url
- end
-
- # Given a Hash of options, generates a route
- def rewrite_path(options)
- options = options.symbolize_keys
- options.update(options[:params].symbolize_keys) if options[:params]
-
- if (overwrite = options.delete(:overwrite_params))
- options.update(@parameters.symbolize_keys)
- options.update(overwrite.symbolize_keys)
- end
-
- RESERVED_OPTIONS.each { |k| options.delete(k) }
-
- # Generates the query string, too
- Routing::Routes.generate(options, @request.symbolized_path_parameters)
- end
-
- def rewrite_authentication(options)
- if options[:user] && options[:password]
- "#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@"
- else
- ""
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb
new file mode 100644
index 0000000000..52b66c9303
--- /dev/null
+++ b/actionpack/lib/action_controller/url_rewriter.rb
@@ -0,0 +1,204 @@
+module ActionController
+ # In routes.rb one defines URL-to-controller mappings, but the reverse
+ # is also possible: an URL can be generated from one of your routing definitions.
+ # URL generation functionality is centralized in this module.
+ #
+ # See ActionController::Routing and ActionController::Resources for general
+ # information about routing and routes.rb.
+ #
+ # Tip: If you need to generate URLs from your models or some other place,
+ # then ActionController::UrlWriter is what you're looking for. Read on for
+ # an introduction.
+ #
+ # == URL generation from parameters
+ #
+ # As you may know, some functions - such as ActionController::Base#url_for
+ # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
+ # of parameters. For example, you've probably had the chance to write code
+ # like this in one of your views:
+ #
+ # <%= link_to('Click here', :controller => 'users',
+ # :action => 'new', :message => 'Welcome!') %>
+ #
+ # #=> Generates a link to: /users/new?message=Welcome%21
+ #
+ # link_to, and all other functions that require URL generation functionality,
+ # actually use ActionController::UrlWriter under the hood. And in particular,
+ # they use the ActionController::UrlWriter#url_for method. One can generate
+ # the same path as the above example by using the following code:
+ #
+ # include UrlWriter
+ # url_for(:controller => 'users',
+ # :action => 'new',
+ # :message => 'Welcome!',
+ # :only_path => true)
+ # # => "/users/new?message=Welcome%21"
+ #
+ # Notice the :only_path => true part. This is because UrlWriter has no
+ # information about the website hostname that your Rails app is serving. So if you
+ # want to include the hostname as well, then you must also pass the :host
+ # argument:
+ #
+ # include UrlWriter
+ # url_for(:controller => 'users',
+ # :action => 'new',
+ # :message => 'Welcome!',
+ # :host => 'www.example.com') # Changed this.
+ # # => "http://www.example.com/users/new?message=Welcome%21"
+ #
+ # By default, all controllers and views have access to a special version of url_for,
+ # that already knows what the current hostname is. So if you use url_for in your
+ # controllers or your views, then you don't need to explicitly pass the :host
+ # argument.
+ #
+ # For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
+ # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
+ # in full. However, mailers don't have hostname information, and what's why you'll still
+ # have to specify the :host argument when generating URLs in mailers.
+ #
+ #
+ # == URL generation for named routes
+ #
+ # UrlWriter also allows one to access methods that have been auto-generated from
+ # named routes. For example, suppose that you have a 'users' resource in your
+ # routes.rb:
+ #
+ # map.resources :users
+ #
+ # This generates, among other things, the method users_path. By default,
+ # this method is accessible from your controllers, views and mailers. If you need
+ # to access this auto-generated method from other places (such as a model), then
+ # you can do that by including ActionController::UrlWriter in your class:
+ #
+ # class User < ActiveRecord::Base
+ # include ActionController::UrlWriter
+ #
+ # def base_uri
+ # user_path(self)
+ # end
+ # end
+ #
+ # User.find(1).base_uri # => "/users/1"
+ module UrlWriter
+ def self.included(base) #:nodoc:
+ ActionController::Routing::Routes.install_helpers(base)
+ base.mattr_accessor :default_url_options
+
+ # The default options for urls written by this writer. Typically a :host pair is provided.
+ base.default_url_options ||= {}
+ end
+
+ # Generate a url based on the options provided, default_url_options and the
+ # routes defined in routes.rb. The following options are supported:
+ #
+ # * :only_path - If true, the relative url is returned. Defaults to +false+.
+ # * :protocol - The protocol to connect to. Defaults to 'http'.
+ # * :host - Specifies the host the link should be targeted at.
+ # If :only_path is false, this option must be
+ # provided either explicitly, or via +default_url_options+.
+ # * :port - Optionally specify the port to connect to.
+ # * :anchor - An anchor name to be appended to the path.
+ # * :skip_relative_url_root - If true, the url is not constructed using the
+ # +relative_url_root+ set in ActionController::Base.relative_url_root.
+ # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2009/"
+ #
+ # Any other key (:controller, :action, etc.) given to
+ # +url_for+ is forwarded to the Routes module.
+ #
+ # Examples:
+ #
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
+ # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
+ def url_for(options)
+ options = self.class.default_url_options.merge(options)
+
+ url = ''
+
+ unless options.delete(:only_path)
+ url << (options.delete(:protocol) || 'http')
+ url << '://' unless url.match("://")
+
+ raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
+
+ url << options.delete(:host)
+ url << ":#{options.delete(:port)}" if options.key?(:port)
+ else
+ # Delete the unused options to prevent their appearance in the query string.
+ [:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
+ end
+ trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
+ url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
+ anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
+ generated = Routing::Routes.generate(options, {})
+ url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
+ url << anchor if anchor
+
+ url
+ end
+ end
+
+ # Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
+ class UrlRewriter #:nodoc:
+ RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
+ def initialize(request, parameters)
+ @request, @parameters = request, parameters
+ end
+
+ def rewrite(options = {})
+ rewrite_url(options)
+ end
+
+ def to_str
+ "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}"
+ end
+
+ alias_method :to_s, :to_str
+
+ private
+ # Given a path and options, returns a rewritten URL string
+ def rewrite_url(options)
+ rewritten_url = ""
+
+ unless options[:only_path]
+ rewritten_url << (options[:protocol] || @request.protocol)
+ rewritten_url << "://" unless rewritten_url.match("://")
+ rewritten_url << rewrite_authentication(options)
+ rewritten_url << (options[:host] || @request.host_with_port)
+ rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
+ end
+
+ path = rewrite_path(options)
+ rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
+ rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ rewritten_url << "##{CGI.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
+
+ rewritten_url
+ end
+
+ # Given a Hash of options, generates a route
+ def rewrite_path(options)
+ options = options.symbolize_keys
+ options.update(options[:params].symbolize_keys) if options[:params]
+
+ if (overwrite = options.delete(:overwrite_params))
+ options.update(@parameters.symbolize_keys)
+ options.update(overwrite.symbolize_keys)
+ end
+
+ RESERVED_OPTIONS.each { |k| options.delete(k) }
+
+ # Generates the query string, too
+ Routing::Routes.generate(options, @request.symbolized_path_parameters)
+ end
+
+ def rewrite_authentication(options)
+ if options[:user] && options[:password]
+ "#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@"
+ else
+ ""
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 11cd812695..259814a322 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -41,6 +41,8 @@ module ActionDispatch
autoload :Static, 'action_dispatch/middleware/static'
autoload :StringCoercion, 'action_dispatch/middleware/string_coercion'
+ autoload :Routing, 'action_dispatch/routing'
+
autoload :Assertions, 'action_dispatch/testing/assertions'
autoload :Integration, 'action_dispatch/testing/integration'
autoload :IntegrationTest, 'action_dispatch/testing/integration'
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
new file mode 100644
index 0000000000..5a8df76326
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -0,0 +1,381 @@
+require 'active_support/core_ext/object/conversions'
+require 'active_support/core_ext/boolean/conversions'
+require 'active_support/core_ext/nil/conversions'
+require 'active_support/core_ext/regexp'
+
+module ActionDispatch
+ # == Routing
+ #
+ # The routing module provides URL rewriting in native Ruby. It's a way to
+ # redirect incoming requests to controllers and actions. This replaces
+ # mod_rewrite rules. Best of all, Rails' Routing works with any web server.
+ # Routes are defined in config/routes.rb.
+ #
+ # Consider the following route, installed by Rails when you generate your
+ # application:
+ #
+ # map.connect ':controller/:action/:id'
+ #
+ # This route states that it expects requests to consist of a
+ # :controller followed by an :action that in turn is fed
+ # some :id.
+ #
+ # Suppose you get an incoming request for /blog/edit/22, you'll end up
+ # with:
+ #
+ # params = { :controller => 'blog',
+ # :action => 'edit',
+ # :id => '22'
+ # }
+ #
+ # Think of creating routes as drawing a map for your requests. The map tells
+ # them where to go based on some predefined pattern:
+ #
+ # ActionController::Routing::Routes.draw do |map|
+ # Pattern 1 tells some request to go to one place
+ # Pattern 2 tell them to go to another
+ # ...
+ # end
+ #
+ # The following symbols are special:
+ #
+ # :controller maps to your controller name
+ # :action maps to an action with your controllers
+ #
+ # Other names simply map to a parameter as in the case of :id.
+ #
+ # == Route priority
+ #
+ # Not all routes are created equally. Routes have priority defined by the
+ # order of appearance of the routes in the config/routes.rb file. The priority goes
+ # from top to bottom. The last route in that file is at the lowest priority
+ # and will be applied last. If no route matches, 404 is returned.
+ #
+ # Within blocks, the empty pattern is at the highest priority.
+ # In practice this works out nicely:
+ #
+ # ActionController::Routing::Routes.draw do |map|
+ # map.with_options :controller => 'blog' do |blog|
+ # blog.show '', :action => 'list'
+ # end
+ # map.connect ':controller/:action/:view'
+ # end
+ #
+ # In this case, invoking blog controller (with an URL like '/blog/')
+ # without parameters will activate the 'list' action by default.
+ #
+ # == Defaults routes and default parameters
+ #
+ # Setting a default route is straightforward in Rails - you simply append a
+ # Hash at the end of your mapping to set any default parameters.
+ #
+ # Example:
+ #
+ # ActionController::Routing:Routes.draw do |map|
+ # map.connect ':controller/:action/:id', :controller => 'blog'
+ # end
+ #
+ # This sets up +blog+ as the default controller if no other is specified.
+ # This means visiting '/' would invoke the blog controller.
+ #
+ # More formally, you can include arbitrary parameters in the route, thus:
+ #
+ # map.connect ':controller/:action/:id', :action => 'show', :page => 'Dashboard'
+ #
+ # This will pass the :page parameter to all incoming requests that match this route.
+ #
+ # Note: The default routes, as provided by the Rails generator, make all actions in every
+ # controller accessible via GET requests. You should consider removing them or commenting
+ # them out if you're using named routes and resources.
+ #
+ # == Named routes
+ #
+ # Routes can be named with the syntax map.name_of_route options,
+ # allowing for easy reference within your source as +name_of_route_url+
+ # for the full URL and +name_of_route_path+ for the URI path.
+ #
+ # Example:
+ #
+ # # In routes.rb
+ # map.login 'login', :controller => 'accounts', :action => 'login'
+ #
+ # # With render, redirect_to, tests, etc.
+ # redirect_to login_url
+ #
+ # Arguments can be passed as well.
+ #
+ # redirect_to show_item_path(:id => 25)
+ #
+ # Use map.root as a shorthand to name a route for the root path "".
+ #
+ # # In routes.rb
+ # map.root :controller => 'blogs'
+ #
+ # # would recognize http://www.example.com/ as
+ # params = { :controller => 'blogs', :action => 'index' }
+ #
+ # # and provide these named routes
+ # root_url # => 'http://www.example.com/'
+ # root_path # => ''
+ #
+ # You can also specify an already-defined named route in your map.root call:
+ #
+ # # In routes.rb
+ # map.new_session :controller => 'sessions', :action => 'new'
+ # map.root :new_session
+ #
+ # Note: when using +with_options+, the route is simply named after the
+ # method you call on the block parameter rather than map.
+ #
+ # # In routes.rb
+ # map.with_options :controller => 'blog' do |blog|
+ # blog.show '', :action => 'list'
+ # blog.delete 'delete/:id', :action => 'delete'
+ # blog.edit 'edit/:id', :action => 'edit'
+ # end
+ #
+ # # provides named routes for show, delete, and edit
+ # link_to @article.title, show_path(:id => @article.id)
+ #
+ # == Pretty URLs
+ #
+ # Routes can generate pretty URLs. For example:
+ #
+ # map.connect 'articles/:year/:month/:day',
+ # :controller => 'articles',
+ # :action => 'find_by_date',
+ # :year => /\d{4}/,
+ # :month => /\d{1,2}/,
+ # :day => /\d{1,2}/
+ #
+ # Using the route above, the URL "http://localhost:3000/articles/2005/11/06"
+ # maps to
+ #
+ # params = {:year => '2005', :month => '11', :day => '06'}
+ #
+ # == Regular Expressions and parameters
+ # You can specify a regular expression to define a format for a parameter.
+ #
+ # map.geocode 'geocode/:postalcode', :controller => 'geocode',
+ # :action => 'show', :postalcode => /\d{5}(-\d{4})?/
+ #
+ # or, more formally:
+ #
+ # map.geocode 'geocode/:postalcode', :controller => 'geocode',
+ # :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ }
+ #
+ # Formats can include the 'ignorecase' and 'extended syntax' regular
+ # expression modifiers:
+ #
+ # map.geocode 'geocode/:postalcode', :controller => 'geocode',
+ # :action => 'show', :postalcode => /hx\d\d\s\d[a-z]{2}/i
+ #
+ # map.geocode 'geocode/:postalcode', :controller => 'geocode',
+ # :action => 'show',:requirements => {
+ # :postalcode => /# Postcode format
+ # \d{5} #Prefix
+ # (-\d{4})? #Suffix
+ # /x
+ # }
+ #
+ # Using the multiline match modifier will raise an ArgumentError.
+ # Encoding regular expression modifiers are silently ignored. The
+ # match will always use the default encoding or ASCII.
+ #
+ # == Route globbing
+ #
+ # Specifying *[string] as part of a rule like:
+ #
+ # map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
+ #
+ # will glob all remaining parts of the route that were not recognized earlier.
+ # The globbed values are in params[:path] as an array of path segments.
+ #
+ # == Route conditions
+ #
+ # With conditions you can define restrictions on routes. Currently the only valid condition is :method.
+ #
+ # * :method - Allows you to specify which method can access the route. Possible values are :post,
+ # :get, :put, :delete and :any. The default value is :any,
+ # :any means that any method can access the route.
+ #
+ # Example:
+ #
+ # map.connect 'post/:id', :controller => 'posts', :action => 'show',
+ # :conditions => { :method => :get }
+ # map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
+ # :conditions => { :method => :post }
+ #
+ # Now, if you POST to /posts/:id, it will route to the create_comment action. A GET on the same
+ # URL will route to the show action.
+ #
+ # == Reloading routes
+ #
+ # You can reload routes if you feel you must:
+ #
+ # ActionController::Routing::Routes.reload
+ #
+ # This will clear all named routes and reload routes.rb if the file has been modified from
+ # last load. To absolutely force reloading, use reload!.
+ #
+ # == Testing Routes
+ #
+ # The two main methods for testing your routes:
+ #
+ # === +assert_routing+
+ #
+ # def test_movie_route_properly_splits
+ # opts = {:controller => "plugin", :action => "checkout", :id => "2"}
+ # assert_routing "plugin/checkout/2", opts
+ # end
+ #
+ # +assert_routing+ lets you test whether or not the route properly resolves into options.
+ #
+ # === +assert_recognizes+
+ #
+ # def test_route_has_options
+ # opts = {:controller => "plugin", :action => "show", :id => "12"}
+ # assert_recognizes opts, "/plugins/show/12"
+ # end
+ #
+ # Note the subtle difference between the two: +assert_routing+ tests that
+ # a URL fits options while +assert_recognizes+ tests that a URL
+ # breaks into parameters properly.
+ #
+ # In tests you can simply pass the URL or named route to +get+ or +post+.
+ #
+ # def send_to_jail
+ # get '/jail'
+ # assert_response :success
+ # assert_template "jail/front"
+ # end
+ #
+ # def goes_to_login
+ # get login_url
+ # #...
+ # end
+ #
+ # == View a list of all your routes
+ #
+ # Run rake routes.
+ #
+ module Routing
+ autoload :Resources, 'action_dispatch/routing/resources'
+ autoload :RouteSet, 'action_dispatch/routing/route_set'
+
+ SEPARATORS = %w( / . ? )
+
+ HTTP_METHODS = [:get, :head, :post, :put, :delete, :options]
+
+ ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
+
+ # The root paths which may contain controller files
+ mattr_accessor :controller_paths
+ self.controller_paths = []
+
+ # A helper module to hold URL related helpers.
+ module Helpers
+ include ActionController::PolymorphicRoutes
+ end
+
+ class << self
+ # Expects an array of controller names as the first argument.
+ # Executes the passed block with only the named controllers named available.
+ # This method is used in internal Rails testing.
+ def with_controllers(names)
+ prior_controllers = @possible_controllers
+ use_controllers! names
+ yield
+ ensure
+ use_controllers! prior_controllers
+ end
+
+ # Returns an array of paths, cleaned of double-slashes and relative path references.
+ # * "\\\" and "//" become "\\" or "/".
+ # * "/foo/bar/../config" becomes "/foo/config".
+ # The returned array is sorted by length, descending.
+ def normalize_paths(paths)
+ # do the hokey-pokey of path normalization...
+ paths = paths.collect do |path|
+ path = path.
+ gsub("//", "/"). # replace double / chars with a single
+ gsub("\\\\", "\\"). # replace double \ chars with a single
+ gsub(%r{(.)[\\/]$}, '\1') # drop final / or \ if path ends with it
+
+ # eliminate .. paths where possible
+ re = %r{[^/\\]+[/\\]\.\.[/\\]}
+ path.gsub!(re, "") while path.match(re)
+ path
+ end
+
+ # start with longest path, first
+ paths = paths.uniq.sort_by { |path| - path.length }
+ end
+
+ # Returns the array of controller names currently available to ActionController::Routing.
+ def possible_controllers
+ unless @possible_controllers
+ @possible_controllers = []
+
+ paths = controller_paths.select { |path| File.directory?(path) && path != "." }
+
+ seen_paths = Hash.new {|h, k| h[k] = true; false}
+ normalize_paths(paths).each do |load_path|
+ Dir["#{load_path}/**/*_controller.rb"].collect do |path|
+ next if seen_paths[path.gsub(%r{^\.[/\\]}, "")]
+
+ controller_name = path[(load_path.length + 1)..-1]
+
+ controller_name.gsub!(/_controller\.rb\Z/, '')
+ @possible_controllers << controller_name
+ end
+ end
+
+ # remove duplicates
+ @possible_controllers.uniq!
+ end
+ @possible_controllers
+ end
+
+ # Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
+ # ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
+ def use_controllers!(controller_names)
+ @possible_controllers = controller_names
+ end
+
+ # Returns a controller path for a new +controller+ based on a +previous+ controller path.
+ # Handles 4 scenarios:
+ #
+ # * stay in the previous controller:
+ # controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
+ #
+ # * stay in the previous namespace:
+ # controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
+ #
+ # * forced move to the root namespace:
+ # controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
+ #
+ # * previous namespace is root:
+ # controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
+ #
+ def controller_relative_to(controller, previous)
+ if controller.nil? then previous
+ elsif controller[0] == ?/ then controller[1..-1]
+ elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
+ else controller
+ end
+ end
+ end
+
+ ActiveSupport::Inflector.module_eval do
+ # Ensures that routes are reloaded when Rails inflections are updated.
+ def inflections_with_route_reloading(&block)
+ returning(inflections_without_route_reloading(&block)) {
+ ActionDispatch::Routing::Routes.reload! if block_given?
+ }
+ end
+
+ alias_method_chain :inflections, :route_reloading
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/resources.rb b/actionpack/lib/action_dispatch/routing/resources.rb
new file mode 100644
index 0000000000..ada0d0a648
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing/resources.rb
@@ -0,0 +1,687 @@
+require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/object/try'
+
+module ActionDispatch
+ module Routing
+ # == Overview
+ #
+ # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
+ # is something that can be pointed at and it will respond with a representation of the data requested.
+ # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
+ # requests XML data.
+ #
+ # RESTful design is based on the assumption that there are four generic verbs that a user of an
+ # application can request from a \resource (the noun).
+ #
+ # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
+ # denotes the type of action that should take place.
+ #
+ # === The Different Methods and their Usage
+ #
+ # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
+ # * POST - Creation of \resources.
+ # * PUT - Editing of attributes on a \resource.
+ # * DELETE - Deletion of a \resource.
+ #
+ # === Examples
+ #
+ # # A GET request on the Posts resource is asking for all Posts
+ # GET /posts
+ #
+ # # A GET request on a single Post resource is asking for that particular Post
+ # GET /posts/1
+ #
+ # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
+ # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
+ #
+ # # A PUT request on a single Post resource is asking for a Post to be updated
+ # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
+ #
+ # # A DELETE request on a single Post resource is asking for it to be deleted
+ # DELETE /posts # with => { :id => 1 }
+ #
+ # By using the REST convention, users of our application can assume certain things about how the data
+ # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
+ # supplying you with methods to create them in your routes.rb file.
+ #
+ # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
+ module Resources
+ INHERITABLE_OPTIONS = :namespace, :shallow
+
+ class Resource #:nodoc:
+ DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
+
+ attr_reader :collection_methods, :member_methods, :new_methods
+ attr_reader :path_prefix, :name_prefix, :path_segment
+ attr_reader :plural, :singular
+ attr_reader :options
+
+ def initialize(entities, options)
+ @plural ||= entities
+ @singular ||= options[:singular] || plural.to_s.singularize
+ @path_segment = options.delete(:as) || @plural
+
+ @options = options
+
+ arrange_actions
+ add_default_actions
+ set_allowed_actions
+ set_prefixes
+ end
+
+ def controller
+ @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
+ end
+
+ def requirements(with_id = false)
+ @requirements ||= @options[:requirements] || {}
+ @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
+
+ with_id ? @requirements.merge(@id_requirement) : @requirements
+ end
+
+ def conditions
+ @conditions ||= @options[:conditions] || {}
+ end
+
+ def path
+ @path ||= "#{path_prefix}/#{path_segment}"
+ end
+
+ def new_path
+ new_action = self.options[:path_names][:new] if self.options[:path_names]
+ new_action ||= ActionController::Base.resources_path_names[:new]
+ @new_path ||= "#{path}/#{new_action}"
+ end
+
+ def shallow_path_prefix
+ @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
+ end
+
+ def member_path
+ @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
+ end
+
+ def nesting_path_prefix
+ @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
+ end
+
+ def shallow_name_prefix
+ @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
+ end
+
+ def nesting_name_prefix
+ "#{shallow_name_prefix}#{singular}_"
+ end
+
+ def action_separator
+ @action_separator ||= ActionController::Base.resource_action_separator
+ end
+
+ def uncountable?
+ @singular.to_s == @plural.to_s
+ end
+
+ def has_action?(action)
+ !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
+ end
+
+ protected
+ def arrange_actions
+ @collection_methods = arrange_actions_by_methods(options.delete(:collection))
+ @member_methods = arrange_actions_by_methods(options.delete(:member))
+ @new_methods = arrange_actions_by_methods(options.delete(:new))
+ end
+
+ def add_default_actions
+ add_default_action(member_methods, :get, :edit)
+ add_default_action(new_methods, :get, :new)
+ end
+
+ def set_allowed_actions
+ only, except = @options.values_at(:only, :except)
+ @allowed_actions ||= {}
+
+ if only == :all || except == :none
+ only = nil
+ except = []
+ elsif only == :none || except == :all
+ only = []
+ except = nil
+ end
+
+ if only
+ @allowed_actions[:only] = Array(only).map {|a| a.to_sym }
+ elsif except
+ @allowed_actions[:except] = Array(except).map {|a| a.to_sym }
+ end
+ end
+
+ def action_allowed?(action)
+ only, except = @allowed_actions.values_at(:only, :except)
+ (!only || only.include?(action)) && (!except || !except.include?(action))
+ end
+
+ def set_prefixes
+ @path_prefix = options.delete(:path_prefix)
+ @name_prefix = options.delete(:name_prefix)
+ end
+
+ def arrange_actions_by_methods(actions)
+ (actions || {}).inject({}) do |flipped_hash, (key, value)|
+ (flipped_hash[value] ||= []) << key
+ flipped_hash
+ end
+ end
+
+ def add_default_action(collection, method, action)
+ (collection[method] ||= []).unshift(action)
+ end
+ end
+
+ class SingletonResource < Resource #:nodoc:
+ def initialize(entity, options)
+ @singular = @plural = entity
+ options[:controller] ||= @singular.to_s.pluralize
+ super
+ end
+
+ alias_method :shallow_path_prefix, :path_prefix
+ alias_method :shallow_name_prefix, :name_prefix
+ alias_method :member_path, :path
+ alias_method :nesting_path_prefix, :path
+ end
+
+ # Creates named routes for implementing verb-oriented controllers
+ # for a collection \resource.
+ #
+ # For example:
+ #
+ # map.resources :messages
+ #
+ # will map the following actions in the corresponding controller:
+ #
+ # class MessagesController < ActionController::Base
+ # # GET messages_url
+ # def index
+ # # return all messages
+ # end
+ #
+ # # GET new_message_url
+ # def new
+ # # return an HTML form for describing a new message
+ # end
+ #
+ # # POST messages_url
+ # def create
+ # # create a new message
+ # end
+ #
+ # # GET message_url(:id => 1)
+ # def show
+ # # find and return a specific message
+ # end
+ #
+ # # GET edit_message_url(:id => 1)
+ # def edit
+ # # return an HTML form for editing a specific message
+ # end
+ #
+ # # PUT message_url(:id => 1)
+ # def update
+ # # find and update a specific message
+ # end
+ #
+ # # DELETE message_url(:id => 1)
+ # def destroy
+ # # delete a specific message
+ # end
+ # end
+ #
+ # Along with the routes themselves, +resources+ generates named routes for use in
+ # controllers and views. map.resources :messages produces the following named routes and helpers:
+ #
+ # Named Route Helpers
+ # ============ =====================================================
+ # messages messages_url, hash_for_messages_url,
+ # messages_path, hash_for_messages_path
+ #
+ # message message_url(id), hash_for_message_url(id),
+ # message_path(id), hash_for_message_path(id)
+ #
+ # new_message new_message_url, hash_for_new_message_url,
+ # new_message_path, hash_for_new_message_path
+ #
+ # edit_message edit_message_url(id), hash_for_edit_message_url(id),
+ # edit_message_path(id), hash_for_edit_message_path(id)
+ #
+ # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
+ #
+ # redirect_to :controller => 'messages', :action => 'index'
+ # # and
+ # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
+ #
+ # now become:
+ #
+ # redirect_to messages_url
+ # # and
+ # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
+ #
+ # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
+ # form tags. The form helpers make this a little easier. For an update form with a @message object:
+ #
+ # <%= form_tag message_path(@message), :method => :put %>
+ #
+ # or
+ #
+ # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
+ #
+ # or
+ #
+ # <% form_for @message do |f| %>
+ #
+ # which takes into account whether @message is a new record or not and generates the
+ # path and method accordingly.
+ #
+ # The +resources+ method accepts the following options to customize the resulting routes:
+ # * :collection - Add named routes for other actions that operate on the collection.
+ # Takes a hash of #{action} => #{method}, where method is :get/:post/:put/:delete,
+ # an array of any of the previous, or :any if the method does not matter.
+ # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
+ # * :member - Same as :collection, but for actions that operate on a specific member.
+ # * :new - Same as :collection, but for actions that operate on the new \resource action.
+ # * :controller - Specify the controller name for the routes.
+ # * :singular - Specify the singular name used in the member routes.
+ # * :requirements - Set custom routing parameter requirements; this is a hash of either
+ # regular expressions (which must match for the route to match) or extra parameters. For example:
+ #
+ # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
+ #
+ # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
+ # * :conditions - Specify custom routing recognition conditions. \Resources sets the :method value for the method-specific routes.
+ # * :as - Specify a different \resource name to use in the URL path. For example:
+ # # products_path == '/productos'
+ # map.resources :products, :as => 'productos' do |product|
+ # # product_reviews_path(product) == '/productos/1234/comentarios'
+ # product.resources :product_reviews, :as => 'comentarios'
+ # end
+ #
+ # * :has_one - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
+ # * :has_many - Same has :has_one, but for plural \resources.
+ #
+ # You may directly specify the routing association with +has_one+ and +has_many+ like:
+ #
+ # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
+ #
+ # This is the same as:
+ #
+ # map.resources :notes do |notes|
+ # notes.resource :author
+ # notes.resources :comments
+ # notes.resources :attachments
+ # end
+ #
+ # * :path_names - Specify different path names for the actions. For example:
+ # # new_products_path == '/productos/nuevo'
+ # # bids_product_path(1) == '/productos/1/licitacoes'
+ # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
+ #
+ # You can also set default action names from an environment, like this:
+ # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
+ #
+ # * :path_prefix - Set a prefix to the routes with required route variables.
+ #
+ # Weblog comments usually belong to a post, so you might use +resources+ like:
+ #
+ # map.resources :articles
+ # map.resources :comments, :path_prefix => '/articles/:article_id'
+ #
+ # You can nest +resources+ calls to set this automatically:
+ #
+ # map.resources :articles do |article|
+ # article.resources :comments
+ # end
+ #
+ # The comment \resources work the same, but must now include a value for :article_id.
+ #
+ # article_comments_url(@article)
+ # article_comment_url(@article, @comment)
+ #
+ # article_comments_url(:article_id => @article)
+ # article_comment_url(:article_id => @article, :id => @comment)
+ #
+ # If you don't want to load all objects from the database you might want to use the article_id directly:
+ #
+ # articles_comments_url(@comment.article_id, @comment)
+ #
+ # * :name_prefix - Define a prefix for all generated routes, usually ending in an underscore.
+ # Use this if you have named routes that may clash.
+ #
+ # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
+ # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
+ #
+ # You may also use :name_prefix to override the generic named routes in a nested \resource:
+ #
+ # map.resources :articles do |article|
+ # article.resources :comments, :name_prefix => nil
+ # end
+ #
+ # This will yield named \resources like so:
+ #
+ # comments_url(@article)
+ # comment_url(@article, @comment)
+ #
+ # * :shallow - If true, paths for nested resources which reference a specific member
+ # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
+ #
+ # The :shallow option is inherited by any nested resource(s).
+ #
+ # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
+ #
+ # map.resources :users, :shallow => true do |user|
+ # user.resources :posts do |post|
+ # post.resources :comments
+ # end
+ # end
+ # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
+ # # also adds the usual named route called "user_posts"
+ # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
+ # # also adds the named route called "post"
+ # # --> GET /posts/2/comments (maps to the CommentsController#index action)
+ # # also adds the named route called "post_comments"
+ # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
+ # # also adds the named route called "comment"
+ #
+ # You may also use :shallow in combination with the +has_one+ and +has_many+ shorthand notations like:
+ #
+ # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
+ #
+ # * :only and :except - Specify which of the seven default actions should be routed to.
+ #
+ # :only and :except may be set to :all, :none, an action name or a
+ # list of action names. By default, routes are generated for all seven actions.
+ #
+ # For example:
+ #
+ # map.resources :posts, :only => [:index, :show] do |post|
+ # post.resources :comments, :except => [:update, :destroy]
+ # end
+ # # --> GET /posts (maps to the PostsController#index action)
+ # # --> POST /posts (fails)
+ # # --> GET /posts/1 (maps to the PostsController#show action)
+ # # --> DELETE /posts/1 (fails)
+ # # --> POST /posts/1/comments (maps to the CommentsController#create action)
+ # # --> PUT /posts/1/comments/1 (fails)
+ #
+ # If map.resources is called with multiple resources, they all get the same options applied.
+ #
+ # Examples:
+ #
+ # map.resources :messages, :path_prefix => "/thread/:thread_id"
+ # # --> GET /thread/7/messages/1
+ #
+ # map.resources :messages, :collection => { :rss => :get }
+ # # --> GET /messages/rss (maps to the #rss action)
+ # # also adds a named route called "rss_messages"
+ #
+ # map.resources :messages, :member => { :mark => :post }
+ # # --> POST /messages/1/mark (maps to the #mark action)
+ # # also adds a named route called "mark_message"
+ #
+ # map.resources :messages, :new => { :preview => :post }
+ # # --> POST /messages/new/preview (maps to the #preview action)
+ # # also adds a named route called "preview_new_message"
+ #
+ # map.resources :messages, :new => { :new => :any, :preview => :post }
+ # # --> POST /messages/new/preview (maps to the #preview action)
+ # # also adds a named route called "preview_new_message"
+ # # --> /messages/new can be invoked via any request method
+ #
+ # map.resources :messages, :controller => "categories",
+ # :path_prefix => "/category/:category_id",
+ # :name_prefix => "category_"
+ # # --> GET /categories/7/messages/1
+ # # has named route "category_message"
+ #
+ # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
+ # HTTP POST on new_message_url will raise a RoutingError exception. The default route in
+ # config/routes.rb overrides this and allows invalid HTTP methods for \resource routes.
+ def resources(*entities, &block)
+ options = entities.extract_options!
+ entities.each { |entity| map_resource(entity, options.dup, &block) }
+ end
+
+ # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
+ # A singleton \resource is global to its current context. For unnested singleton \resources,
+ # the \resource is global to the current user visiting the application, such as a user's
+ # /account profile. For nested singleton \resources, the \resource is global to its parent
+ # \resource, such as a projects \resource that has_one :project_manager.
+ # The project_manager should be mapped as a singleton \resource under projects:
+ #
+ # map.resources :projects do |project|
+ # project.resource :project_manager
+ # end
+ #
+ # See +resources+ for general conventions. These are the main differences:
+ # * A singular name is given to map.resource. The default controller name is still taken from the plural name.
+ # * To specify a custom plural name, use the :plural option. There is no :singular option.
+ # * No default index route is created for the singleton \resource controller.
+ # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
+ #
+ # For example:
+ #
+ # map.resource :account
+ #
+ # maps these actions in the Accounts controller:
+ #
+ # class AccountsController < ActionController::Base
+ # # GET new_account_url
+ # def new
+ # # return an HTML form for describing the new account
+ # end
+ #
+ # # POST account_url
+ # def create
+ # # create an account
+ # end
+ #
+ # # GET account_url
+ # def show
+ # # find and return the account
+ # end
+ #
+ # # GET edit_account_url
+ # def edit
+ # # return an HTML form for editing the account
+ # end
+ #
+ # # PUT account_url
+ # def update
+ # # find and update the account
+ # end
+ #
+ # # DELETE account_url
+ # def destroy
+ # # delete the account
+ # end
+ # end
+ #
+ # Along with the routes themselves, +resource+ generates named routes for
+ # use in controllers and views. map.resource :account produces
+ # these named routes and helpers:
+ #
+ # Named Route Helpers
+ # ============ =============================================
+ # account account_url, hash_for_account_url,
+ # account_path, hash_for_account_path
+ #
+ # new_account new_account_url, hash_for_new_account_url,
+ # new_account_path, hash_for_new_account_path
+ #
+ # edit_account edit_account_url, hash_for_edit_account_url,
+ # edit_account_path, hash_for_edit_account_path
+ def resource(*entities, &block)
+ options = entities.extract_options!
+ entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
+ end
+
+ private
+ def map_resource(entities, options = {}, &block)
+ resource = Resource.new(entities, options)
+
+ with_options :controller => resource.controller do |map|
+ map_associations(resource, options)
+
+ if block_given?
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
+ end
+
+ map_collection_actions(map, resource)
+ map_default_collection_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
+ end
+ end
+
+ def map_singleton_resource(entities, options = {}, &block)
+ resource = SingletonResource.new(entities, options)
+
+ with_options :controller => resource.controller do |map|
+ map_associations(resource, options)
+
+ if block_given?
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
+ end
+
+ map_collection_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
+ map_default_singleton_actions(map, resource)
+ end
+ end
+
+ def map_associations(resource, options)
+ map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
+
+ path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
+ name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
+
+ Array(options[:has_one]).each do |association|
+ resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
+ end
+ end
+
+ def map_has_many_associations(resource, associations, options)
+ case associations
+ when Hash
+ associations.each do |association,has_many|
+ map_has_many_associations(resource, association, options.merge(:has_many => has_many))
+ end
+ when Array
+ associations.each do |association|
+ map_has_many_associations(resource, association, options)
+ end
+ when Symbol, String
+ resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
+ else
+ end
+ end
+
+ def map_collection_actions(map, resource)
+ resource.collection_methods.each do |method, actions|
+ actions.each do |action|
+ [method].flatten.each do |m|
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
+ action_path ||= action
+
+ map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
+ end
+ end
+ end
+ end
+
+ def map_default_collection_actions(map, resource)
+ index_route_name = "#{resource.name_prefix}#{resource.plural}"
+
+ if resource.uncountable?
+ index_route_name << "_index"
+ end
+
+ map_resource_routes(map, resource, :index, resource.path, index_route_name)
+ map_resource_routes(map, resource, :create, resource.path, index_route_name)
+ end
+
+ def map_default_singleton_actions(map, resource)
+ map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
+ end
+
+ def map_new_actions(map, resource)
+ resource.new_methods.each do |method, actions|
+ actions.each do |action|
+ route_path = resource.new_path
+ route_name = "new_#{resource.name_prefix}#{resource.singular}"
+
+ unless action == :new
+ route_path = "#{route_path}#{resource.action_separator}#{action}"
+ route_name = "#{action}_#{route_name}"
+ end
+
+ map_resource_routes(map, resource, action, route_path, route_name, method)
+ end
+ end
+ end
+
+ def map_member_actions(map, resource)
+ resource.member_methods.each do |method, actions|
+ actions.each do |action|
+ [method].flatten.each do |m|
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
+ action_path ||= ActionController::Base.resources_path_names[action] || action
+
+ map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
+ end
+ end
+ end
+
+ route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
+ map_resource_routes(map, resource, :show, resource.member_path, route_path)
+ map_resource_routes(map, resource, :update, resource.member_path, route_path)
+ map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
+ end
+
+ def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
+ if resource.has_action?(action)
+ action_options = action_options_for(action, resource, method, resource_options)
+ formatted_route_path = "#{route_path}.:format"
+
+ if route_name && @set.named_routes[route_name.to_sym].nil?
+ map.named_route(route_name, formatted_route_path, action_options)
+ else
+ map.connect(formatted_route_path, action_options)
+ end
+ end
+ end
+
+ def add_conditions_for(conditions, method)
+ returning({:conditions => conditions.dup}) do |options|
+ options[:conditions][:method] = method unless method == :any
+ end
+ end
+
+ def action_options_for(action, resource, method = nil, resource_options = {})
+ default_options = { :action => action.to_s }
+ require_id = !resource.kind_of?(SingletonResource)
+ force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
+
+ case default_options[:action]
+ when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
+ when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
+ when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
+ when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
+ when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
+ else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
new file mode 100644
index 0000000000..9e40108d00
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -0,0 +1,699 @@
+require 'rack/mount'
+require 'forwardable'
+
+module ActionDispatch
+ module Routing
+ class RouteSet #:nodoc:
+ NotFound = lambda { |env|
+ raise ActionController::RoutingError, "No route matches #{env[::Rack::Mount::Const::PATH_INFO].inspect} with #{env.inspect}"
+ }
+
+ PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
+
+ class Dispatcher
+ def initialize(options = {})
+ defaults = options[:defaults]
+ @glob_param = options.delete(:glob)
+ end
+
+ def call(env)
+ params = env[PARAMETERS_KEY]
+ merge_default_action!(params)
+ split_glob_param!(params) if @glob_param
+ params.each { |key, value| params[key] = URI.unescape(value) if value.is_a?(String) }
+
+ if env['action_controller.recognize']
+ [200, {}, params]
+ else
+ controller = controller(params)
+ controller.action(params[:action]).call(env)
+ end
+ end
+
+ private
+ def controller(params)
+ if params && params.has_key?(:controller)
+ controller = "#{params[:controller].camelize}Controller"
+ ActiveSupport::Inflector.constantize(controller)
+ end
+ end
+
+ def merge_default_action!(params)
+ params[:action] ||= 'index'
+ end
+
+ def split_glob_param!(params)
+ params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) }
+ end
+ end
+
+ module RouteExtensions
+ def segment_keys
+ conditions[:path_info].names.compact.map { |key| key.to_sym }
+ end
+ end
+
+ # Mapper instances are used to build routes. The object passed to the draw
+ # block in config/routes.rb is a Mapper instance.
+ #
+ # Mapper instances have relatively few instance methods, in order to avoid
+ # clashes with named routes.
+ class Mapper #:doc:
+ include Routing::Resources
+
+ def initialize(set) #:nodoc:
+ @set = set
+ end
+
+ # Create an unnamed route with the provided +path+ and +options+. See
+ # ActionDispatch::Routing for an introduction to routes.
+ def connect(path, options = {})
+ @set.add_route(path, options)
+ end
+
+ # Creates a named route called "root" for matching the root level request.
+ def root(options = {})
+ if options.is_a?(Symbol)
+ if source_route = @set.named_routes.routes[options]
+ options = source_route.defaults.merge({ :conditions => source_route.conditions })
+ end
+ end
+ named_route("root", '', options)
+ end
+
+ def named_route(name, path, options = {}) #:nodoc:
+ @set.add_named_route(name, path, options)
+ end
+
+ # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
+ # Example:
+ #
+ # map.namespace(:admin) do |admin|
+ # admin.resources :products,
+ # :has_many => [ :tags, :images, :variants ]
+ # end
+ #
+ # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
+ # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
+ # Admin::TagsController.
+ def namespace(name, options = {}, &block)
+ if options[:namespace]
+ with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
+ else
+ with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
+ end
+ end
+
+ def method_missing(route_name, *args, &proc) #:nodoc:
+ super unless args.length >= 1 && proc.nil?
+ @set.add_named_route(route_name, *args)
+ end
+ end
+
+ # A NamedRouteCollection instance is a collection of named routes, and also
+ # maintains an anonymous module that can be used to install helpers for the
+ # named routes.
+ class NamedRouteCollection #:nodoc:
+ include Enumerable
+ attr_reader :routes, :helpers
+
+ def initialize
+ clear!
+ end
+
+ def clear!
+ @routes = {}
+ @helpers = []
+
+ @module ||= Module.new
+ @module.instance_methods.each do |selector|
+ @module.class_eval { remove_method selector }
+ end
+ end
+
+ def add(name, route)
+ routes[name.to_sym] = route
+ define_named_route_methods(name, route)
+ end
+
+ def get(name)
+ routes[name.to_sym]
+ end
+
+ alias []= add
+ alias [] get
+ alias clear clear!
+
+ def each
+ routes.each { |name, route| yield name, route }
+ self
+ end
+
+ def names
+ routes.keys
+ end
+
+ def length
+ routes.length
+ end
+
+ def reset!
+ old_routes = routes.dup
+ clear!
+ old_routes.each do |name, route|
+ add(name, route)
+ end
+ end
+
+ def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
+ reset! if regenerate
+ Array(destinations).each do |dest|
+ dest.__send__(:include, @module)
+ end
+ end
+
+ private
+ def url_helper_name(name, kind = :url)
+ :"#{name}_#{kind}"
+ end
+
+ def hash_access_name(name, kind = :url)
+ :"hash_for_#{name}_#{kind}"
+ end
+
+ def define_named_route_methods(name, route)
+ {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
+ hash = route.defaults.merge(:use_route => name).merge(opts)
+ define_hash_access route, name, kind, hash
+ define_url_helper route, name, kind, hash
+ end
+ end
+
+ def named_helper_module_eval(code, *args)
+ @module.module_eval(code, *args)
+ end
+
+ def define_hash_access(route, name, kind, options)
+ selector = hash_access_name(name, kind)
+ named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
+ def #{selector}(options = nil) # def hash_for_users_url(options = nil)
+ options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false}
+ end # end
+ protected :#{selector} # protected :hash_for_users_url
+ end_eval
+ helpers << selector
+ end
+
+ def define_url_helper(route, name, kind, options)
+ selector = url_helper_name(name, kind)
+ # The segment keys used for positional parameters
+
+ hash_access_method = hash_access_name(name, kind)
+
+ # allow ordered parameters to be associated with corresponding
+ # dynamic segments, so you can do
+ #
+ # foo_url(bar, baz, bang)
+ #
+ # instead of
+ #
+ # foo_url(:bar => bar, :baz => baz, :bang => bang)
+ #
+ # Also allow options hash, so you can do
+ #
+ # foo_url(bar, baz, bang, :sort_by => 'baz')
+ #
+ named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
+ def #{selector}(*args) # def users_url(*args)
+ #
+ opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
+ args.first || {} # args.first || {}
+ else # else
+ options = args.extract_options! # options = args.extract_options!
+ args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
+ h[k] = v # h[k] = v
+ h # h
+ end # end
+ options.merge(args) # options.merge(args)
+ end # end
+ #
+ url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
+ #
+ end # end
+ #Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL.
+ def formatted_#{selector}(*args) # def formatted_users_url(*args)
+ ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
+ "formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
+ "Please pass format to the standard " + # "Please pass format to the standard " +
+ "#{selector} method instead.", caller) # "users_url method instead.", caller)
+ #{selector}(*args) # users_url(*args)
+ end # end
+ protected :#{selector} # protected :users_url
+ end_eval
+ helpers << selector
+ end
+ end
+
+ attr_accessor :routes, :named_routes, :configuration_files
+
+ def initialize
+ self.configuration_files = []
+
+ self.routes = []
+ self.named_routes = NamedRouteCollection.new
+
+ clear!
+ end
+
+ def draw
+ clear!
+ yield Mapper.new(self)
+ @set.add_route(NotFound)
+ install_helpers
+ @set.freeze
+ end
+
+ def clear!
+ routes.clear
+ named_routes.clear
+ @set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY)
+ end
+
+ def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
+ Array(destinations).each { |d| d.module_eval { include Helpers } }
+ named_routes.install(destinations, regenerate_code)
+ end
+
+ def empty?
+ routes.empty?
+ end
+
+ def add_configuration_file(path)
+ self.configuration_files << path
+ end
+
+ # Deprecated accessor
+ def configuration_file=(path)
+ add_configuration_file(path)
+ end
+
+ # Deprecated accessor
+ def configuration_file
+ configuration_files
+ end
+
+ def load!
+ Routing.use_controllers!(nil) # Clear the controller cache so we may discover new ones
+ load_routes!
+ end
+
+ # reload! will always force a reload whereas load checks the timestamp first
+ alias reload! load!
+
+ def reload
+ if configuration_files.any? && @routes_last_modified
+ if routes_changed_at == @routes_last_modified
+ return # routes didn't change, don't reload
+ else
+ @routes_last_modified = routes_changed_at
+ end
+ end
+
+ load!
+ end
+
+ def load_routes!
+ if configuration_files.any?
+ configuration_files.each { |config| load(config) }
+ @routes_last_modified = routes_changed_at
+ else
+ draw do |map|
+ map.connect ":controller/:action/:id"
+ end
+ end
+ end
+
+ def routes_changed_at
+ routes_changed_at = nil
+
+ configuration_files.each do |config|
+ config_changed_at = File.stat(config).mtime
+
+ if routes_changed_at.nil? || config_changed_at > routes_changed_at
+ routes_changed_at = config_changed_at
+ end
+ end
+
+ routes_changed_at
+ end
+
+ def add_route(path, options = {})
+ options = options.dup
+
+ if conditions = options.delete(:conditions)
+ conditions = conditions.dup
+ method = [conditions.delete(:method)].flatten.compact
+ method.map! { |m|
+ m = m.to_s.upcase
+
+ if m == "HEAD"
+ raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
+ end
+
+ unless HTTP_METHODS.include?(m.downcase.to_sym)
+ raise ArgumentError, "Invalid HTTP method specified in route conditions"
+ end
+
+ m
+ }
+
+ if method.length > 1
+ method = Regexp.union(*method)
+ elsif method.length == 1
+ method = method.first
+ else
+ method = nil
+ end
+ end
+
+ path_prefix = options.delete(:path_prefix)
+ name_prefix = options.delete(:name_prefix)
+ namespace = options.delete(:namespace)
+
+ name = options.delete(:_name)
+ name = "#{name_prefix}#{name}" if name_prefix
+
+ requirements = options.delete(:requirements) || {}
+ defaults = options.delete(:defaults) || {}
+ options.each do |k, v|
+ if v.is_a?(Regexp)
+ if value = options.delete(k)
+ requirements[k.to_sym] = value
+ end
+ else
+ value = options.delete(k)
+ defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
+ end
+ end
+
+ requirements.each do |_, requirement|
+ if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
+ end
+ if requirement.multiline?
+ raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
+ end
+ end
+
+ possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
+ requirements[:controller] ||= Regexp.union(*possible_names)
+
+ if defaults[:controller]
+ defaults[:action] ||= 'index'
+ defaults[:controller] = defaults[:controller].to_s
+ defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
+ end
+
+ if defaults[:action]
+ defaults[:action] = defaults[:action].to_s
+ end
+
+ if path.is_a?(String)
+ path = "#{path_prefix}/#{path}" if path_prefix
+ path = path.gsub('.:format', '(.:format)')
+ path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
+ glob = $1.to_sym if path =~ /\/\*(\w+)$/
+ path = ::Rack::Mount::Utils.normalize_path(path)
+ path = ::Rack::Mount::Strexp.compile(path, requirements, %w( / . ? ))
+
+ if glob && !defaults[glob].blank?
+ raise ActionController::RoutingError, "paths cannot have non-empty default values"
+ end
+ end
+
+ app = Dispatcher.new(:defaults => defaults, :glob => glob)
+
+ conditions = {}
+ conditions[:request_method] = method if method
+ conditions[:path_info] = path if path
+
+ route = @set.add_route(app, conditions, defaults, name)
+ route.extend(RouteExtensions)
+ routes << route
+ route
+ end
+
+ def add_named_route(name, path, options = {})
+ options[:_name] = name
+ route = add_route(path, options)
+ named_routes[route.name] = route
+ route
+ end
+
+ def options_as_params(options)
+ # If an explicit :controller was given, always make :action explicit
+ # too, so that action expiry works as expected for things like
+ #
+ # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
+ #
+ # (the above is from the unit tests). In the above case, because the
+ # controller was explicitly given, but no action, the action is implied to
+ # be "index", not the recalled action of "show".
+ #
+ # great fun, eh?
+
+ options_as_params = options.clone
+ options_as_params[:action] ||= 'index' if options[:controller]
+ options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
+ options_as_params
+ end
+
+ def build_expiry(options, recall)
+ recall.inject({}) do |expiry, (key, recalled_value)|
+ expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
+ expiry
+ end
+ end
+
+ # Generate the path indicated by the arguments, and return an array of
+ # the keys that were not used to generate it.
+ def extra_keys(options, recall={})
+ generate_extras(options, recall).last
+ end
+
+ def generate_extras(options, recall={})
+ generate(options, recall, :generate_extras)
+ end
+
+ def generate(options, recall = {}, method = :generate)
+ options, recall = options.dup, recall.dup
+ named_route = options.delete(:use_route)
+
+ options = options_as_params(options)
+ expire_on = build_expiry(options, recall)
+
+ recall[:action] ||= 'index' if options[:controller] || recall[:controller]
+
+ if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller])
+ options[:controller] = recall.delete(:controller)
+
+ if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action])
+ options[:action] = recall.delete(:action)
+
+ if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id])
+ options[:id] = recall.delete(:id)
+ end
+ end
+ end
+
+ options[:controller] = options[:controller].to_s if options[:controller]
+
+ if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
+ old_parts = recall[:controller].split('/')
+ new_parts = options[:controller].split('/')
+ parts = old_parts[0..-(new_parts.length + 1)] + new_parts
+ options[:controller] = parts.join('/')
+ end
+
+ options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
+
+ merged = options.merge(recall)
+ if options.has_key?(:action) && options[:action].nil?
+ options.delete(:action)
+ recall[:action] = 'index'
+ end
+ recall[:action] = options.delete(:action) if options[:action] == 'index'
+
+ path = _uri(named_route, options, recall)
+ if path && method == :generate_extras
+ uri = URI(path)
+ extras = uri.query ?
+ Rack::Utils.parse_nested_query(uri.query).keys.map { |k| k.to_sym } :
+ []
+ [uri.path, extras]
+ elsif path
+ path
+ else
+ raise ActionController::RoutingError, "No route matches #{options.inspect}"
+ end
+ rescue Rack::Mount::RoutingError
+ raise ActionController::RoutingError, "No route matches #{options.inspect}"
+ end
+
+ def call(env)
+ @set.call(env)
+ rescue ActionController::RoutingError => e
+ raise e if env['action_controller.rescue_error'] == false
+
+ method, path = env['REQUEST_METHOD'].downcase.to_sym, env['PATH_INFO']
+
+ # Route was not recognized. Try to find out why (maybe wrong verb).
+ allows = HTTP_METHODS.select { |verb|
+ begin
+ recognize_path(path, {:method => verb}, false)
+ rescue ActionController::RoutingError
+ nil
+ end
+ }
+
+ if !HTTP_METHODS.include?(method)
+ raise ActionController::NotImplemented.new(*allows)
+ elsif !allows.empty?
+ raise ActionController::MethodNotAllowed.new(*allows)
+ else
+ raise e
+ end
+ end
+
+ def recognize(request)
+ params = recognize_path(request.path, extract_request_environment(request))
+ request.path_parameters = params.with_indifferent_access
+ "#{params[:controller].to_s.camelize}Controller".constantize
+ end
+
+ def recognize_path(path, environment = {}, rescue_error = true)
+ method = (environment[:method] || "GET").to_s.upcase
+
+ begin
+ env = Rack::MockRequest.env_for(path, {:method => method})
+ rescue URI::InvalidURIError => e
+ raise ActionController::RoutingError, e.message
+ end
+
+ env['action_controller.recognize'] = true
+ env['action_controller.rescue_error'] = rescue_error
+ status, headers, body = call(env)
+ body
+ end
+
+ # Subclasses and plugins may override this method to extract further attributes
+ # from the request, for use by route conditions and such.
+ def extract_request_environment(request)
+ { :method => request.method }
+ end
+
+ private
+ def _uri(named_route, params, recall)
+ params = URISegment.wrap_values(params)
+ recall = URISegment.wrap_values(recall)
+
+ unless result = @set.generate(:path_info, named_route, params, recall)
+ return
+ end
+
+ uri, params = result
+ params.each do |k, v|
+ if v._value
+ params[k] = v._value
+ else
+ params.delete(k)
+ end
+ end
+
+ uri << "?#{Rack::Mount::Utils.build_nested_query(params)}" if uri && params.any?
+ uri
+ end
+
+ class URISegment < Struct.new(:_value, :_escape)
+ EXCLUDED = [:controller]
+
+ def self.wrap_values(hash)
+ hash.inject({}) { |h, (k, v)|
+ h[k] = new(v, !EXCLUDED.include?(k.to_sym))
+ h
+ }
+ end
+
+ extend Forwardable
+ def_delegators :_value, :==, :eql?, :hash
+
+ def to_param
+ @to_param ||= begin
+ if _value.is_a?(Array)
+ _value.map { |v| _escaped(v) }.join('/')
+ else
+ _escaped(_value)
+ end
+ end
+ end
+ alias_method :to_s, :to_param
+
+ private
+ def _escaped(value)
+ v = value.respond_to?(:to_param) ? value.to_param : value
+ _escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s
+ end
+ end
+
+ def optionalize_trailing_dynamic_segments(path, requirements, defaults)
+ path = (path =~ /^\//) ? path.dup : "/#{path}"
+ optional, segments = true, []
+
+ required_segments = requirements.keys
+ required_segments -= defaults.keys.compact
+
+ old_segments = path.split('/')
+ old_segments.shift
+ length = old_segments.length
+
+ old_segments.reverse.each_with_index do |segment, index|
+ required_segments.each do |required|
+ if segment =~ /#{required}/
+ optional = false
+ break
+ end
+ end
+
+ if optional
+ if segment == ":id" && segments.include?(":action")
+ optional = false
+ elsif segment == ":controller" || segment == ":action" || segment == ":id"
+ # Ignore
+ elsif !(segment =~ /^:\w+$/) &&
+ !(segment =~ /^:\w+\(\.:format\)$/)
+ optional = false
+ elsif segment =~ /^:(\w+)$/
+ if defaults.has_key?($1.to_sym)
+ defaults.delete($1.to_sym)
+ else
+ optional = false
+ end
+ end
+ end
+
+ if optional && index < length - 1
+ segments.unshift('(/', segment)
+ segments.push(')')
+ elsif optional
+ segments.unshift('/(', segment)
+ segments.push(')')
+ else
+ segments.unshift('/', segment)
+ end
+ end
+
+ segments.join
+ end
+ end
+ end
+end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 5b47de19ae..4f1bafbad1 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -41,7 +41,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_should_arrange_actions
- resource = ActionController::Resources::Resource.new(:messages,
+ resource = ActionDispatch::Routing::Resources::Resource.new(:messages,
:collection => { :rss => :get, :reorder => :post, :csv => :post },
:member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post },
:new => { :preview => :get, :draft => :get })
@@ -54,18 +54,18 @@ class ResourcesTest < ActionController::TestCase
end
def test_should_resource_controller_name_equal_resource_name_by_default
- resource = ActionController::Resources::Resource.new(:messages, {})
+ resource = ActionDispatch::Routing::Resources::Resource.new(:messages, {})
assert_equal 'messages', resource.controller
end
def test_should_resource_controller_name_equal_controller_option
- resource = ActionController::Resources::Resource.new(:messages, :controller => 'posts')
+ resource = ActionDispatch::Routing::Resources::Resource.new(:messages, :controller => 'posts')
assert_equal 'posts', resource.controller
end
def test_should_all_singleton_paths_be_the_same
[ :path, :nesting_path_prefix, :member_path ].each do |method|
- resource = ActionController::Resources::SingletonResource.new(:messages, :path_prefix => 'admin')
+ resource = ActionDispatch::Routing::Resources::SingletonResource.new(:messages, :path_prefix => 'admin')
assert_equal 'admin/messages', resource.send(method)
end
end
@@ -121,7 +121,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_override_paths_for_default_restful_actions
- resource = ActionController::Resources::Resource.new(:messages,
+ resource = ActionDispatch::Routing::Resources::Resource.new(:messages,
:path_names => {:new => 'nuevo', :edit => 'editar'})
assert_equal resource.new_path, "#{resource.path}/nuevo"
end
@@ -135,7 +135,7 @@ class ResourcesTest < ActionController::TestCase
def test_with_custom_conditions
with_restful_routing :messages, :conditions => { :subdomain => 'app' } do
- assert ActionController::Routing::Routes.recognize_path("/messages", :method => :get, :subdomain => 'app')
+ assert ActionDispatch::Routing::Routes.recognize_path("/messages", :method => :get, :subdomain => 'app')
end
end
--
cgit v1.2.3
From 35576a237e2c721dca8be0f8f0d653ae8bc07389 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 20 Oct 2009 10:16:19 -0500
Subject: Add arel to AMo gemfile
---
activemodel/Gemfile | 1 +
1 file changed, 1 insertion(+)
diff --git a/activemodel/Gemfile b/activemodel/Gemfile
index 289d696792..a192fdaf82 100644
--- a/activemodel/Gemfile
+++ b/activemodel/Gemfile
@@ -3,6 +3,7 @@ rails_root = Pathname.new(File.dirname(__FILE__)).join("..")
Gem.sources.each { |uri| source uri }
gem "activesupport", "3.0.pre", :vendored_at => rails_root.join("activesupport")
+gem "arel", :git => "git://github.com/rails/arel.git"
only :test do
gem "sqlite3-ruby"
--
cgit v1.2.3
From df68cae0c0837fbf23fdfc3f04162307ffa8f2c1 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 20 Oct 2009 10:46:27 -0500
Subject: Group together all the old routing dsl logic
---
actionpack/lib/action_controller.rb | 1 -
actionpack/lib/action_controller/deprecated.rb | 1 -
actionpack/lib/action_dispatch/routing.rb | 2 +-
actionpack/lib/action_dispatch/routing/mapper.rb | 879 +++++++++++++++++++++
.../lib/action_dispatch/routing/resources.rb | 687 ----------------
.../lib/action_dispatch/routing/route_set.rb | 206 +----
actionpack/test/controller/resources_test.rb | 10 +-
7 files changed, 887 insertions(+), 899 deletions(-)
create mode 100644 actionpack/lib/action_dispatch/routing/mapper.rb
delete mode 100644 actionpack/lib/action_dispatch/routing/resources.rb
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index b5091f39f9..9db1a71202 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -26,7 +26,6 @@ module ActionController
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
autoload :RecordIdentifier, 'action_controller/record_identifier'
- autoload :Resources, 'action_controller/deprecated'
autoload :Routing, 'action_controller/deprecated'
autoload :SessionManagement, 'action_controller/metal/session_management'
autoload :TestCase, 'action_controller/testing/test_case'
diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb
index 23fe6a4c3a..589061e77c 100644
--- a/actionpack/lib/action_controller/deprecated.rb
+++ b/actionpack/lib/action_controller/deprecated.rb
@@ -1,5 +1,4 @@
ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
ActionController::Routing = ActionDispatch::Routing
-ActionDispatch::Resources = ActionDispatch::Routing::Resources
ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 5a8df76326..0647d051cb 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -260,7 +260,7 @@ module ActionDispatch
# Run rake routes.
#
module Routing
- autoload :Resources, 'action_dispatch/routing/resources'
+ autoload :Mapper, 'action_dispatch/routing/mapper'
autoload :RouteSet, 'action_dispatch/routing/route_set'
SEPARATORS = %w( / . ? )
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
new file mode 100644
index 0000000000..44afbb9cd7
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -0,0 +1,879 @@
+module ActionDispatch
+ module Routing
+ # Mapper instances are used to build routes. The object passed to the draw
+ # block in config/routes.rb is a Mapper instance.
+ #
+ # Mapper instances have relatively few instance methods, in order to avoid
+ # clashes with named routes.
+ #
+ # == Overview
+ #
+ # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
+ # is something that can be pointed at and it will respond with a representation of the data requested.
+ # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
+ # requests XML data.
+ #
+ # RESTful design is based on the assumption that there are four generic verbs that a user of an
+ # application can request from a \resource (the noun).
+ #
+ # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
+ # denotes the type of action that should take place.
+ #
+ # === The Different Methods and their Usage
+ #
+ # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
+ # * POST - Creation of \resources.
+ # * PUT - Editing of attributes on a \resource.
+ # * DELETE - Deletion of a \resource.
+ #
+ # === Examples
+ #
+ # # A GET request on the Posts resource is asking for all Posts
+ # GET /posts
+ #
+ # # A GET request on a single Post resource is asking for that particular Post
+ # GET /posts/1
+ #
+ # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
+ # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
+ #
+ # # A PUT request on a single Post resource is asking for a Post to be updated
+ # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
+ #
+ # # A DELETE request on a single Post resource is asking for it to be deleted
+ # DELETE /posts # with => { :id => 1 }
+ #
+ # By using the REST convention, users of our application can assume certain things about how the data
+ # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
+ # supplying you with methods to create them in your routes.rb file.
+ #
+ # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
+ class Mapper #:doc:
+ def initialize(set) #:nodoc:
+ @set = set
+ end
+
+ # Create an unnamed route with the provided +path+ and +options+. See
+ # ActionDispatch::Routing for an introduction to routes.
+ def connect(path, options = {})
+ options = options.dup
+
+ if conditions = options.delete(:conditions)
+ conditions = conditions.dup
+ method = [conditions.delete(:method)].flatten.compact
+ method.map! { |m|
+ m = m.to_s.upcase
+
+ if m == "HEAD"
+ raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
+ end
+
+ unless HTTP_METHODS.include?(m.downcase.to_sym)
+ raise ArgumentError, "Invalid HTTP method specified in route conditions"
+ end
+
+ m
+ }
+
+ if method.length > 1
+ method = Regexp.union(*method)
+ elsif method.length == 1
+ method = method.first
+ else
+ method = nil
+ end
+ end
+
+ path_prefix = options.delete(:path_prefix)
+ name_prefix = options.delete(:name_prefix)
+ namespace = options.delete(:namespace)
+
+ name = options.delete(:_name)
+ name = "#{name_prefix}#{name}" if name_prefix
+
+ requirements = options.delete(:requirements) || {}
+ defaults = options.delete(:defaults) || {}
+ options.each do |k, v|
+ if v.is_a?(Regexp)
+ if value = options.delete(k)
+ requirements[k.to_sym] = value
+ end
+ else
+ value = options.delete(k)
+ defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
+ end
+ end
+
+ requirements.each do |_, requirement|
+ if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
+ end
+ if requirement.multiline?
+ raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
+ end
+ end
+
+ possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
+ requirements[:controller] ||= Regexp.union(*possible_names)
+
+ if defaults[:controller]
+ defaults[:action] ||= 'index'
+ defaults[:controller] = defaults[:controller].to_s
+ defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
+ end
+
+ if defaults[:action]
+ defaults[:action] = defaults[:action].to_s
+ end
+
+ if path.is_a?(String)
+ path = "#{path_prefix}/#{path}" if path_prefix
+ path = path.gsub('.:format', '(.:format)')
+ path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
+ glob = $1.to_sym if path =~ /\/\*(\w+)$/
+ path = ::Rack::Mount::Utils.normalize_path(path)
+ path = ::Rack::Mount::Strexp.compile(path, requirements, %w( / . ? ))
+
+ if glob && !defaults[glob].blank?
+ raise ActionController::RoutingError, "paths cannot have non-empty default values"
+ end
+ end
+
+ app = Routing::RouteSet::Dispatcher.new(:defaults => defaults, :glob => glob)
+
+ conditions = {}
+ conditions[:request_method] = method if method
+ conditions[:path_info] = path if path
+
+ @set.add_route(app, conditions, defaults, name)
+ end
+
+ def optionalize_trailing_dynamic_segments(path, requirements, defaults) #:nodoc:
+ path = (path =~ /^\//) ? path.dup : "/#{path}"
+ optional, segments = true, []
+
+ required_segments = requirements.keys
+ required_segments -= defaults.keys.compact
+
+ old_segments = path.split('/')
+ old_segments.shift
+ length = old_segments.length
+
+ old_segments.reverse.each_with_index do |segment, index|
+ required_segments.each do |required|
+ if segment =~ /#{required}/
+ optional = false
+ break
+ end
+ end
+
+ if optional
+ if segment == ":id" && segments.include?(":action")
+ optional = false
+ elsif segment == ":controller" || segment == ":action" || segment == ":id"
+ # Ignore
+ elsif !(segment =~ /^:\w+$/) &&
+ !(segment =~ /^:\w+\(\.:format\)$/)
+ optional = false
+ elsif segment =~ /^:(\w+)$/
+ if defaults.has_key?($1.to_sym)
+ defaults.delete($1.to_sym)
+ else
+ optional = false
+ end
+ end
+ end
+
+ if optional && index < length - 1
+ segments.unshift('(/', segment)
+ segments.push(')')
+ elsif optional
+ segments.unshift('/(', segment)
+ segments.push(')')
+ else
+ segments.unshift('/', segment)
+ end
+ end
+
+ segments.join
+ end
+ private :optionalize_trailing_dynamic_segments
+
+ # Creates a named route called "root" for matching the root level request.
+ def root(options = {})
+ if options.is_a?(Symbol)
+ if source_route = @set.named_routes.routes[options]
+ options = source_route.defaults.merge({ :conditions => source_route.conditions })
+ end
+ end
+ named_route("root", '', options)
+ end
+
+ def named_route(name, path, options = {}) #:nodoc:
+ options[:_name] = name
+ connect(path, options)
+ end
+
+ # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
+ # Example:
+ #
+ # map.namespace(:admin) do |admin|
+ # admin.resources :products,
+ # :has_many => [ :tags, :images, :variants ]
+ # end
+ #
+ # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
+ # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
+ # Admin::TagsController.
+ def namespace(name, options = {}, &block)
+ if options[:namespace]
+ with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
+ else
+ with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
+ end
+ end
+
+ def method_missing(route_name, *args, &proc) #:nodoc:
+ super unless args.length >= 1 && proc.nil?
+ named_route(route_name, *args)
+ end
+
+ INHERITABLE_OPTIONS = :namespace, :shallow
+
+ class Resource #:nodoc:
+ DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
+
+ attr_reader :collection_methods, :member_methods, :new_methods
+ attr_reader :path_prefix, :name_prefix, :path_segment
+ attr_reader :plural, :singular
+ attr_reader :options
+
+ def initialize(entities, options)
+ @plural ||= entities
+ @singular ||= options[:singular] || plural.to_s.singularize
+ @path_segment = options.delete(:as) || @plural
+
+ @options = options
+
+ arrange_actions
+ add_default_actions
+ set_allowed_actions
+ set_prefixes
+ end
+
+ def controller
+ @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
+ end
+
+ def requirements(with_id = false)
+ @requirements ||= @options[:requirements] || {}
+ @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
+
+ with_id ? @requirements.merge(@id_requirement) : @requirements
+ end
+
+ def conditions
+ @conditions ||= @options[:conditions] || {}
+ end
+
+ def path
+ @path ||= "#{path_prefix}/#{path_segment}"
+ end
+
+ def new_path
+ new_action = self.options[:path_names][:new] if self.options[:path_names]
+ new_action ||= ActionController::Base.resources_path_names[:new]
+ @new_path ||= "#{path}/#{new_action}"
+ end
+
+ def shallow_path_prefix
+ @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
+ end
+
+ def member_path
+ @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
+ end
+
+ def nesting_path_prefix
+ @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
+ end
+
+ def shallow_name_prefix
+ @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
+ end
+
+ def nesting_name_prefix
+ "#{shallow_name_prefix}#{singular}_"
+ end
+
+ def action_separator
+ @action_separator ||= ActionController::Base.resource_action_separator
+ end
+
+ def uncountable?
+ @singular.to_s == @plural.to_s
+ end
+
+ def has_action?(action)
+ !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
+ end
+
+ protected
+ def arrange_actions
+ @collection_methods = arrange_actions_by_methods(options.delete(:collection))
+ @member_methods = arrange_actions_by_methods(options.delete(:member))
+ @new_methods = arrange_actions_by_methods(options.delete(:new))
+ end
+
+ def add_default_actions
+ add_default_action(member_methods, :get, :edit)
+ add_default_action(new_methods, :get, :new)
+ end
+
+ def set_allowed_actions
+ only, except = @options.values_at(:only, :except)
+ @allowed_actions ||= {}
+
+ if only == :all || except == :none
+ only = nil
+ except = []
+ elsif only == :none || except == :all
+ only = []
+ except = nil
+ end
+
+ if only
+ @allowed_actions[:only] = Array(only).map {|a| a.to_sym }
+ elsif except
+ @allowed_actions[:except] = Array(except).map {|a| a.to_sym }
+ end
+ end
+
+ def action_allowed?(action)
+ only, except = @allowed_actions.values_at(:only, :except)
+ (!only || only.include?(action)) && (!except || !except.include?(action))
+ end
+
+ def set_prefixes
+ @path_prefix = options.delete(:path_prefix)
+ @name_prefix = options.delete(:name_prefix)
+ end
+
+ def arrange_actions_by_methods(actions)
+ (actions || {}).inject({}) do |flipped_hash, (key, value)|
+ (flipped_hash[value] ||= []) << key
+ flipped_hash
+ end
+ end
+
+ def add_default_action(collection, method, action)
+ (collection[method] ||= []).unshift(action)
+ end
+ end
+
+ class SingletonResource < Resource #:nodoc:
+ def initialize(entity, options)
+ @singular = @plural = entity
+ options[:controller] ||= @singular.to_s.pluralize
+ super
+ end
+
+ alias_method :shallow_path_prefix, :path_prefix
+ alias_method :shallow_name_prefix, :name_prefix
+ alias_method :member_path, :path
+ alias_method :nesting_path_prefix, :path
+ end
+
+ # Creates named routes for implementing verb-oriented controllers
+ # for a collection \resource.
+ #
+ # For example:
+ #
+ # map.resources :messages
+ #
+ # will map the following actions in the corresponding controller:
+ #
+ # class MessagesController < ActionController::Base
+ # # GET messages_url
+ # def index
+ # # return all messages
+ # end
+ #
+ # # GET new_message_url
+ # def new
+ # # return an HTML form for describing a new message
+ # end
+ #
+ # # POST messages_url
+ # def create
+ # # create a new message
+ # end
+ #
+ # # GET message_url(:id => 1)
+ # def show
+ # # find and return a specific message
+ # end
+ #
+ # # GET edit_message_url(:id => 1)
+ # def edit
+ # # return an HTML form for editing a specific message
+ # end
+ #
+ # # PUT message_url(:id => 1)
+ # def update
+ # # find and update a specific message
+ # end
+ #
+ # # DELETE message_url(:id => 1)
+ # def destroy
+ # # delete a specific message
+ # end
+ # end
+ #
+ # Along with the routes themselves, +resources+ generates named routes for use in
+ # controllers and views. map.resources :messages produces the following named routes and helpers:
+ #
+ # Named Route Helpers
+ # ============ =====================================================
+ # messages messages_url, hash_for_messages_url,
+ # messages_path, hash_for_messages_path
+ #
+ # message message_url(id), hash_for_message_url(id),
+ # message_path(id), hash_for_message_path(id)
+ #
+ # new_message new_message_url, hash_for_new_message_url,
+ # new_message_path, hash_for_new_message_path
+ #
+ # edit_message edit_message_url(id), hash_for_edit_message_url(id),
+ # edit_message_path(id), hash_for_edit_message_path(id)
+ #
+ # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
+ #
+ # redirect_to :controller => 'messages', :action => 'index'
+ # # and
+ # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
+ #
+ # now become:
+ #
+ # redirect_to messages_url
+ # # and
+ # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
+ #
+ # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
+ # form tags. The form helpers make this a little easier. For an update form with a @message object:
+ #
+ # <%= form_tag message_path(@message), :method => :put %>
+ #
+ # or
+ #
+ # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
+ #
+ # or
+ #
+ # <% form_for @message do |f| %>
+ #
+ # which takes into account whether @message is a new record or not and generates the
+ # path and method accordingly.
+ #
+ # The +resources+ method accepts the following options to customize the resulting routes:
+ # * :collection - Add named routes for other actions that operate on the collection.
+ # Takes a hash of #{action} => #{method}, where method is :get/:post/:put/:delete,
+ # an array of any of the previous, or :any if the method does not matter.
+ # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
+ # * :member - Same as :collection, but for actions that operate on a specific member.
+ # * :new - Same as :collection, but for actions that operate on the new \resource action.
+ # * :controller - Specify the controller name for the routes.
+ # * :singular - Specify the singular name used in the member routes.
+ # * :requirements - Set custom routing parameter requirements; this is a hash of either
+ # regular expressions (which must match for the route to match) or extra parameters. For example:
+ #
+ # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
+ #
+ # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
+ # * :conditions - Specify custom routing recognition conditions. \Resources sets the :method value for the method-specific routes.
+ # * :as - Specify a different \resource name to use in the URL path. For example:
+ # # products_path == '/productos'
+ # map.resources :products, :as => 'productos' do |product|
+ # # product_reviews_path(product) == '/productos/1234/comentarios'
+ # product.resources :product_reviews, :as => 'comentarios'
+ # end
+ #
+ # * :has_one - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
+ # * :has_many - Same has :has_one, but for plural \resources.
+ #
+ # You may directly specify the routing association with +has_one+ and +has_many+ like:
+ #
+ # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
+ #
+ # This is the same as:
+ #
+ # map.resources :notes do |notes|
+ # notes.resource :author
+ # notes.resources :comments
+ # notes.resources :attachments
+ # end
+ #
+ # * :path_names - Specify different path names for the actions. For example:
+ # # new_products_path == '/productos/nuevo'
+ # # bids_product_path(1) == '/productos/1/licitacoes'
+ # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
+ #
+ # You can also set default action names from an environment, like this:
+ # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
+ #
+ # * :path_prefix - Set a prefix to the routes with required route variables.
+ #
+ # Weblog comments usually belong to a post, so you might use +resources+ like:
+ #
+ # map.resources :articles
+ # map.resources :comments, :path_prefix => '/articles/:article_id'
+ #
+ # You can nest +resources+ calls to set this automatically:
+ #
+ # map.resources :articles do |article|
+ # article.resources :comments
+ # end
+ #
+ # The comment \resources work the same, but must now include a value for :article_id.
+ #
+ # article_comments_url(@article)
+ # article_comment_url(@article, @comment)
+ #
+ # article_comments_url(:article_id => @article)
+ # article_comment_url(:article_id => @article, :id => @comment)
+ #
+ # If you don't want to load all objects from the database you might want to use the article_id directly:
+ #
+ # articles_comments_url(@comment.article_id, @comment)
+ #
+ # * :name_prefix - Define a prefix for all generated routes, usually ending in an underscore.
+ # Use this if you have named routes that may clash.
+ #
+ # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
+ # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
+ #
+ # You may also use :name_prefix to override the generic named routes in a nested \resource:
+ #
+ # map.resources :articles do |article|
+ # article.resources :comments, :name_prefix => nil
+ # end
+ #
+ # This will yield named \resources like so:
+ #
+ # comments_url(@article)
+ # comment_url(@article, @comment)
+ #
+ # * :shallow - If true, paths for nested resources which reference a specific member
+ # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
+ #
+ # The :shallow option is inherited by any nested resource(s).
+ #
+ # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
+ #
+ # map.resources :users, :shallow => true do |user|
+ # user.resources :posts do |post|
+ # post.resources :comments
+ # end
+ # end
+ # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
+ # # also adds the usual named route called "user_posts"
+ # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
+ # # also adds the named route called "post"
+ # # --> GET /posts/2/comments (maps to the CommentsController#index action)
+ # # also adds the named route called "post_comments"
+ # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
+ # # also adds the named route called "comment"
+ #
+ # You may also use :shallow in combination with the +has_one+ and +has_many+ shorthand notations like:
+ #
+ # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
+ #
+ # * :only and :except - Specify which of the seven default actions should be routed to.
+ #
+ # :only and :except may be set to :all, :none, an action name or a
+ # list of action names. By default, routes are generated for all seven actions.
+ #
+ # For example:
+ #
+ # map.resources :posts, :only => [:index, :show] do |post|
+ # post.resources :comments, :except => [:update, :destroy]
+ # end
+ # # --> GET /posts (maps to the PostsController#index action)
+ # # --> POST /posts (fails)
+ # # --> GET /posts/1 (maps to the PostsController#show action)
+ # # --> DELETE /posts/1 (fails)
+ # # --> POST /posts/1/comments (maps to the CommentsController#create action)
+ # # --> PUT /posts/1/comments/1 (fails)
+ #
+ # If map.resources is called with multiple resources, they all get the same options applied.
+ #
+ # Examples:
+ #
+ # map.resources :messages, :path_prefix => "/thread/:thread_id"
+ # # --> GET /thread/7/messages/1
+ #
+ # map.resources :messages, :collection => { :rss => :get }
+ # # --> GET /messages/rss (maps to the #rss action)
+ # # also adds a named route called "rss_messages"
+ #
+ # map.resources :messages, :member => { :mark => :post }
+ # # --> POST /messages/1/mark (maps to the #mark action)
+ # # also adds a named route called "mark_message"
+ #
+ # map.resources :messages, :new => { :preview => :post }
+ # # --> POST /messages/new/preview (maps to the #preview action)
+ # # also adds a named route called "preview_new_message"
+ #
+ # map.resources :messages, :new => { :new => :any, :preview => :post }
+ # # --> POST /messages/new/preview (maps to the #preview action)
+ # # also adds a named route called "preview_new_message"
+ # # --> /messages/new can be invoked via any request method
+ #
+ # map.resources :messages, :controller => "categories",
+ # :path_prefix => "/category/:category_id",
+ # :name_prefix => "category_"
+ # # --> GET /categories/7/messages/1
+ # # has named route "category_message"
+ #
+ # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
+ # HTTP POST on new_message_url will raise a RoutingError exception. The default route in
+ # config/routes.rb overrides this and allows invalid HTTP methods for \resource routes.
+ def resources(*entities, &block)
+ options = entities.extract_options!
+ entities.each { |entity| map_resource(entity, options.dup, &block) }
+ end
+
+ # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
+ # A singleton \resource is global to its current context. For unnested singleton \resources,
+ # the \resource is global to the current user visiting the application, such as a user's
+ # /account profile. For nested singleton \resources, the \resource is global to its parent
+ # \resource, such as a projects \resource that has_one :project_manager.
+ # The project_manager should be mapped as a singleton \resource under projects:
+ #
+ # map.resources :projects do |project|
+ # project.resource :project_manager
+ # end
+ #
+ # See +resources+ for general conventions. These are the main differences:
+ # * A singular name is given to map.resource. The default controller name is still taken from the plural name.
+ # * To specify a custom plural name, use the :plural option. There is no :singular option.
+ # * No default index route is created for the singleton \resource controller.
+ # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
+ #
+ # For example:
+ #
+ # map.resource :account
+ #
+ # maps these actions in the Accounts controller:
+ #
+ # class AccountsController < ActionController::Base
+ # # GET new_account_url
+ # def new
+ # # return an HTML form for describing the new account
+ # end
+ #
+ # # POST account_url
+ # def create
+ # # create an account
+ # end
+ #
+ # # GET account_url
+ # def show
+ # # find and return the account
+ # end
+ #
+ # # GET edit_account_url
+ # def edit
+ # # return an HTML form for editing the account
+ # end
+ #
+ # # PUT account_url
+ # def update
+ # # find and update the account
+ # end
+ #
+ # # DELETE account_url
+ # def destroy
+ # # delete the account
+ # end
+ # end
+ #
+ # Along with the routes themselves, +resource+ generates named routes for
+ # use in controllers and views. map.resource :account produces
+ # these named routes and helpers:
+ #
+ # Named Route Helpers
+ # ============ =============================================
+ # account account_url, hash_for_account_url,
+ # account_path, hash_for_account_path
+ #
+ # new_account new_account_url, hash_for_new_account_url,
+ # new_account_path, hash_for_new_account_path
+ #
+ # edit_account edit_account_url, hash_for_edit_account_url,
+ # edit_account_path, hash_for_edit_account_path
+ def resource(*entities, &block)
+ options = entities.extract_options!
+ entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
+ end
+
+ private
+ def map_resource(entities, options = {}, &block)
+ resource = Resource.new(entities, options)
+
+ with_options :controller => resource.controller do |map|
+ map_associations(resource, options)
+
+ if block_given?
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
+ end
+
+ map_collection_actions(map, resource)
+ map_default_collection_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
+ end
+ end
+
+ def map_singleton_resource(entities, options = {}, &block)
+ resource = SingletonResource.new(entities, options)
+
+ with_options :controller => resource.controller do |map|
+ map_associations(resource, options)
+
+ if block_given?
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
+ end
+
+ map_collection_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
+ map_default_singleton_actions(map, resource)
+ end
+ end
+
+ def map_associations(resource, options)
+ map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
+
+ path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
+ name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
+
+ Array(options[:has_one]).each do |association|
+ resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
+ end
+ end
+
+ def map_has_many_associations(resource, associations, options)
+ case associations
+ when Hash
+ associations.each do |association,has_many|
+ map_has_many_associations(resource, association, options.merge(:has_many => has_many))
+ end
+ when Array
+ associations.each do |association|
+ map_has_many_associations(resource, association, options)
+ end
+ when Symbol, String
+ resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
+ else
+ end
+ end
+
+ def map_collection_actions(map, resource)
+ resource.collection_methods.each do |method, actions|
+ actions.each do |action|
+ [method].flatten.each do |m|
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
+ action_path ||= action
+
+ map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
+ end
+ end
+ end
+ end
+
+ def map_default_collection_actions(map, resource)
+ index_route_name = "#{resource.name_prefix}#{resource.plural}"
+
+ if resource.uncountable?
+ index_route_name << "_index"
+ end
+
+ map_resource_routes(map, resource, :index, resource.path, index_route_name)
+ map_resource_routes(map, resource, :create, resource.path, index_route_name)
+ end
+
+ def map_default_singleton_actions(map, resource)
+ map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
+ end
+
+ def map_new_actions(map, resource)
+ resource.new_methods.each do |method, actions|
+ actions.each do |action|
+ route_path = resource.new_path
+ route_name = "new_#{resource.name_prefix}#{resource.singular}"
+
+ unless action == :new
+ route_path = "#{route_path}#{resource.action_separator}#{action}"
+ route_name = "#{action}_#{route_name}"
+ end
+
+ map_resource_routes(map, resource, action, route_path, route_name, method)
+ end
+ end
+ end
+
+ def map_member_actions(map, resource)
+ resource.member_methods.each do |method, actions|
+ actions.each do |action|
+ [method].flatten.each do |m|
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
+ action_path ||= ActionController::Base.resources_path_names[action] || action
+
+ map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
+ end
+ end
+ end
+
+ route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
+ map_resource_routes(map, resource, :show, resource.member_path, route_path)
+ map_resource_routes(map, resource, :update, resource.member_path, route_path)
+ map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
+ end
+
+ def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
+ if resource.has_action?(action)
+ action_options = action_options_for(action, resource, method, resource_options)
+ formatted_route_path = "#{route_path}.:format"
+
+ if route_name && @set.named_routes[route_name.to_sym].nil?
+ map.named_route(route_name, formatted_route_path, action_options)
+ else
+ map.connect(formatted_route_path, action_options)
+ end
+ end
+ end
+
+ def add_conditions_for(conditions, method)
+ returning({:conditions => conditions.dup}) do |options|
+ options[:conditions][:method] = method unless method == :any
+ end
+ end
+
+ def action_options_for(action, resource, method = nil, resource_options = {})
+ default_options = { :action => action.to_s }
+ require_id = !resource.kind_of?(SingletonResource)
+ force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
+
+ case default_options[:action]
+ when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
+ when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
+ when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
+ when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
+ when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
+ else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/resources.rb b/actionpack/lib/action_dispatch/routing/resources.rb
deleted file mode 100644
index ada0d0a648..0000000000
--- a/actionpack/lib/action_dispatch/routing/resources.rb
+++ /dev/null
@@ -1,687 +0,0 @@
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/object/try'
-
-module ActionDispatch
- module Routing
- # == Overview
- #
- # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
- # is something that can be pointed at and it will respond with a representation of the data requested.
- # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
- # requests XML data.
- #
- # RESTful design is based on the assumption that there are four generic verbs that a user of an
- # application can request from a \resource (the noun).
- #
- # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
- # denotes the type of action that should take place.
- #
- # === The Different Methods and their Usage
- #
- # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
- # * POST - Creation of \resources.
- # * PUT - Editing of attributes on a \resource.
- # * DELETE - Deletion of a \resource.
- #
- # === Examples
- #
- # # A GET request on the Posts resource is asking for all Posts
- # GET /posts
- #
- # # A GET request on a single Post resource is asking for that particular Post
- # GET /posts/1
- #
- # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
- # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
- #
- # # A PUT request on a single Post resource is asking for a Post to be updated
- # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
- #
- # # A DELETE request on a single Post resource is asking for it to be deleted
- # DELETE /posts # with => { :id => 1 }
- #
- # By using the REST convention, users of our application can assume certain things about how the data
- # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
- # supplying you with methods to create them in your routes.rb file.
- #
- # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
- module Resources
- INHERITABLE_OPTIONS = :namespace, :shallow
-
- class Resource #:nodoc:
- DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
-
- attr_reader :collection_methods, :member_methods, :new_methods
- attr_reader :path_prefix, :name_prefix, :path_segment
- attr_reader :plural, :singular
- attr_reader :options
-
- def initialize(entities, options)
- @plural ||= entities
- @singular ||= options[:singular] || plural.to_s.singularize
- @path_segment = options.delete(:as) || @plural
-
- @options = options
-
- arrange_actions
- add_default_actions
- set_allowed_actions
- set_prefixes
- end
-
- def controller
- @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
- end
-
- def requirements(with_id = false)
- @requirements ||= @options[:requirements] || {}
- @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
-
- with_id ? @requirements.merge(@id_requirement) : @requirements
- end
-
- def conditions
- @conditions ||= @options[:conditions] || {}
- end
-
- def path
- @path ||= "#{path_prefix}/#{path_segment}"
- end
-
- def new_path
- new_action = self.options[:path_names][:new] if self.options[:path_names]
- new_action ||= ActionController::Base.resources_path_names[:new]
- @new_path ||= "#{path}/#{new_action}"
- end
-
- def shallow_path_prefix
- @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
- end
-
- def member_path
- @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
- end
-
- def nesting_path_prefix
- @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
- end
-
- def shallow_name_prefix
- @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
- end
-
- def nesting_name_prefix
- "#{shallow_name_prefix}#{singular}_"
- end
-
- def action_separator
- @action_separator ||= ActionController::Base.resource_action_separator
- end
-
- def uncountable?
- @singular.to_s == @plural.to_s
- end
-
- def has_action?(action)
- !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
- end
-
- protected
- def arrange_actions
- @collection_methods = arrange_actions_by_methods(options.delete(:collection))
- @member_methods = arrange_actions_by_methods(options.delete(:member))
- @new_methods = arrange_actions_by_methods(options.delete(:new))
- end
-
- def add_default_actions
- add_default_action(member_methods, :get, :edit)
- add_default_action(new_methods, :get, :new)
- end
-
- def set_allowed_actions
- only, except = @options.values_at(:only, :except)
- @allowed_actions ||= {}
-
- if only == :all || except == :none
- only = nil
- except = []
- elsif only == :none || except == :all
- only = []
- except = nil
- end
-
- if only
- @allowed_actions[:only] = Array(only).map {|a| a.to_sym }
- elsif except
- @allowed_actions[:except] = Array(except).map {|a| a.to_sym }
- end
- end
-
- def action_allowed?(action)
- only, except = @allowed_actions.values_at(:only, :except)
- (!only || only.include?(action)) && (!except || !except.include?(action))
- end
-
- def set_prefixes
- @path_prefix = options.delete(:path_prefix)
- @name_prefix = options.delete(:name_prefix)
- end
-
- def arrange_actions_by_methods(actions)
- (actions || {}).inject({}) do |flipped_hash, (key, value)|
- (flipped_hash[value] ||= []) << key
- flipped_hash
- end
- end
-
- def add_default_action(collection, method, action)
- (collection[method] ||= []).unshift(action)
- end
- end
-
- class SingletonResource < Resource #:nodoc:
- def initialize(entity, options)
- @singular = @plural = entity
- options[:controller] ||= @singular.to_s.pluralize
- super
- end
-
- alias_method :shallow_path_prefix, :path_prefix
- alias_method :shallow_name_prefix, :name_prefix
- alias_method :member_path, :path
- alias_method :nesting_path_prefix, :path
- end
-
- # Creates named routes for implementing verb-oriented controllers
- # for a collection \resource.
- #
- # For example:
- #
- # map.resources :messages
- #
- # will map the following actions in the corresponding controller:
- #
- # class MessagesController < ActionController::Base
- # # GET messages_url
- # def index
- # # return all messages
- # end
- #
- # # GET new_message_url
- # def new
- # # return an HTML form for describing a new message
- # end
- #
- # # POST messages_url
- # def create
- # # create a new message
- # end
- #
- # # GET message_url(:id => 1)
- # def show
- # # find and return a specific message
- # end
- #
- # # GET edit_message_url(:id => 1)
- # def edit
- # # return an HTML form for editing a specific message
- # end
- #
- # # PUT message_url(:id => 1)
- # def update
- # # find and update a specific message
- # end
- #
- # # DELETE message_url(:id => 1)
- # def destroy
- # # delete a specific message
- # end
- # end
- #
- # Along with the routes themselves, +resources+ generates named routes for use in
- # controllers and views. map.resources :messages produces the following named routes and helpers:
- #
- # Named Route Helpers
- # ============ =====================================================
- # messages messages_url, hash_for_messages_url,
- # messages_path, hash_for_messages_path
- #
- # message message_url(id), hash_for_message_url(id),
- # message_path(id), hash_for_message_path(id)
- #
- # new_message new_message_url, hash_for_new_message_url,
- # new_message_path, hash_for_new_message_path
- #
- # edit_message edit_message_url(id), hash_for_edit_message_url(id),
- # edit_message_path(id), hash_for_edit_message_path(id)
- #
- # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
- #
- # redirect_to :controller => 'messages', :action => 'index'
- # # and
- # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
- #
- # now become:
- #
- # redirect_to messages_url
- # # and
- # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
- #
- # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
- # form tags. The form helpers make this a little easier. For an update form with a @message object:
- #
- # <%= form_tag message_path(@message), :method => :put %>
- #
- # or
- #
- # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
- #
- # or
- #
- # <% form_for @message do |f| %>
- #
- # which takes into account whether @message is a new record or not and generates the
- # path and method accordingly.
- #
- # The +resources+ method accepts the following options to customize the resulting routes:
- # * :collection - Add named routes for other actions that operate on the collection.
- # Takes a hash of #{action} => #{method}, where method is :get/:post/:put/:delete,
- # an array of any of the previous, or :any if the method does not matter.
- # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
- # * :member - Same as :collection, but for actions that operate on a specific member.
- # * :new - Same as :collection, but for actions that operate on the new \resource action.
- # * :controller - Specify the controller name for the routes.
- # * :singular - Specify the singular name used in the member routes.
- # * :requirements - Set custom routing parameter requirements; this is a hash of either
- # regular expressions (which must match for the route to match) or extra parameters. For example:
- #
- # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
- #
- # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
- # * :conditions - Specify custom routing recognition conditions. \Resources sets the :method value for the method-specific routes.
- # * :as - Specify a different \resource name to use in the URL path. For example:
- # # products_path == '/productos'
- # map.resources :products, :as => 'productos' do |product|
- # # product_reviews_path(product) == '/productos/1234/comentarios'
- # product.resources :product_reviews, :as => 'comentarios'
- # end
- #
- # * :has_one - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
- # * :has_many - Same has :has_one, but for plural \resources.
- #
- # You may directly specify the routing association with +has_one+ and +has_many+ like:
- #
- # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
- #
- # This is the same as:
- #
- # map.resources :notes do |notes|
- # notes.resource :author
- # notes.resources :comments
- # notes.resources :attachments
- # end
- #
- # * :path_names - Specify different path names for the actions. For example:
- # # new_products_path == '/productos/nuevo'
- # # bids_product_path(1) == '/productos/1/licitacoes'
- # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
- #
- # You can also set default action names from an environment, like this:
- # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
- #
- # * :path_prefix - Set a prefix to the routes with required route variables.
- #
- # Weblog comments usually belong to a post, so you might use +resources+ like:
- #
- # map.resources :articles
- # map.resources :comments, :path_prefix => '/articles/:article_id'
- #
- # You can nest +resources+ calls to set this automatically:
- #
- # map.resources :articles do |article|
- # article.resources :comments
- # end
- #
- # The comment \resources work the same, but must now include a value for :article_id.
- #
- # article_comments_url(@article)
- # article_comment_url(@article, @comment)
- #
- # article_comments_url(:article_id => @article)
- # article_comment_url(:article_id => @article, :id => @comment)
- #
- # If you don't want to load all objects from the database you might want to use the article_id directly:
- #
- # articles_comments_url(@comment.article_id, @comment)
- #
- # * :name_prefix - Define a prefix for all generated routes, usually ending in an underscore.
- # Use this if you have named routes that may clash.
- #
- # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
- # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
- #
- # You may also use :name_prefix to override the generic named routes in a nested \resource:
- #
- # map.resources :articles do |article|
- # article.resources :comments, :name_prefix => nil
- # end
- #
- # This will yield named \resources like so:
- #
- # comments_url(@article)
- # comment_url(@article, @comment)
- #
- # * :shallow - If true, paths for nested resources which reference a specific member
- # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
- #
- # The :shallow option is inherited by any nested resource(s).
- #
- # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
- #
- # map.resources :users, :shallow => true do |user|
- # user.resources :posts do |post|
- # post.resources :comments
- # end
- # end
- # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
- # # also adds the usual named route called "user_posts"
- # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
- # # also adds the named route called "post"
- # # --> GET /posts/2/comments (maps to the CommentsController#index action)
- # # also adds the named route called "post_comments"
- # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
- # # also adds the named route called "comment"
- #
- # You may also use :shallow in combination with the +has_one+ and +has_many+ shorthand notations like:
- #
- # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
- #
- # * :only and :except - Specify which of the seven default actions should be routed to.
- #
- # :only and :except may be set to :all, :none, an action name or a
- # list of action names. By default, routes are generated for all seven actions.
- #
- # For example:
- #
- # map.resources :posts, :only => [:index, :show] do |post|
- # post.resources :comments, :except => [:update, :destroy]
- # end
- # # --> GET /posts (maps to the PostsController#index action)
- # # --> POST /posts (fails)
- # # --> GET /posts/1 (maps to the PostsController#show action)
- # # --> DELETE /posts/1 (fails)
- # # --> POST /posts/1/comments (maps to the CommentsController#create action)
- # # --> PUT /posts/1/comments/1 (fails)
- #
- # If map.resources is called with multiple resources, they all get the same options applied.
- #
- # Examples:
- #
- # map.resources :messages, :path_prefix => "/thread/:thread_id"
- # # --> GET /thread/7/messages/1
- #
- # map.resources :messages, :collection => { :rss => :get }
- # # --> GET /messages/rss (maps to the #rss action)
- # # also adds a named route called "rss_messages"
- #
- # map.resources :messages, :member => { :mark => :post }
- # # --> POST /messages/1/mark (maps to the #mark action)
- # # also adds a named route called "mark_message"
- #
- # map.resources :messages, :new => { :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- #
- # map.resources :messages, :new => { :new => :any, :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- # # --> /messages/new can be invoked via any request method
- #
- # map.resources :messages, :controller => "categories",
- # :path_prefix => "/category/:category_id",
- # :name_prefix => "category_"
- # # --> GET /categories/7/messages/1
- # # has named route "category_message"
- #
- # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
- # HTTP POST on new_message_url will raise a RoutingError exception. The default route in
- # config/routes.rb overrides this and allows invalid HTTP methods for \resource routes.
- def resources(*entities, &block)
- options = entities.extract_options!
- entities.each { |entity| map_resource(entity, options.dup, &block) }
- end
-
- # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
- # A singleton \resource is global to its current context. For unnested singleton \resources,
- # the \resource is global to the current user visiting the application, such as a user's
- # /account profile. For nested singleton \resources, the \resource is global to its parent
- # \resource, such as a projects \resource that has_one :project_manager.
- # The project_manager should be mapped as a singleton \resource under projects:
- #
- # map.resources :projects do |project|
- # project.resource :project_manager
- # end
- #
- # See +resources+ for general conventions. These are the main differences:
- # * A singular name is given to map.resource. The default controller name is still taken from the plural name.
- # * To specify a custom plural name, use the :plural option. There is no :singular option.
- # * No default index route is created for the singleton \resource controller.
- # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
- #
- # For example:
- #
- # map.resource :account
- #
- # maps these actions in the Accounts controller:
- #
- # class AccountsController < ActionController::Base
- # # GET new_account_url
- # def new
- # # return an HTML form for describing the new account
- # end
- #
- # # POST account_url
- # def create
- # # create an account
- # end
- #
- # # GET account_url
- # def show
- # # find and return the account
- # end
- #
- # # GET edit_account_url
- # def edit
- # # return an HTML form for editing the account
- # end
- #
- # # PUT account_url
- # def update
- # # find and update the account
- # end
- #
- # # DELETE account_url
- # def destroy
- # # delete the account
- # end
- # end
- #
- # Along with the routes themselves, +resource+ generates named routes for
- # use in controllers and views. map.resource :account produces
- # these named routes and helpers:
- #
- # Named Route Helpers
- # ============ =============================================
- # account account_url, hash_for_account_url,
- # account_path, hash_for_account_path
- #
- # new_account new_account_url, hash_for_new_account_url,
- # new_account_path, hash_for_new_account_path
- #
- # edit_account edit_account_url, hash_for_edit_account_url,
- # edit_account_path, hash_for_edit_account_path
- def resource(*entities, &block)
- options = entities.extract_options!
- entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
- end
-
- private
- def map_resource(entities, options = {}, &block)
- resource = Resource.new(entities, options)
-
- with_options :controller => resource.controller do |map|
- map_associations(resource, options)
-
- if block_given?
- with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
- end
-
- map_collection_actions(map, resource)
- map_default_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
- end
- end
-
- def map_singleton_resource(entities, options = {}, &block)
- resource = SingletonResource.new(entities, options)
-
- with_options :controller => resource.controller do |map|
- map_associations(resource, options)
-
- if block_given?
- with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
- end
-
- map_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
- map_default_singleton_actions(map, resource)
- end
- end
-
- def map_associations(resource, options)
- map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
-
- path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
- name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
-
- Array(options[:has_one]).each do |association|
- resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
- end
- end
-
- def map_has_many_associations(resource, associations, options)
- case associations
- when Hash
- associations.each do |association,has_many|
- map_has_many_associations(resource, association, options.merge(:has_many => has_many))
- end
- when Array
- associations.each do |association|
- map_has_many_associations(resource, association, options)
- end
- when Symbol, String
- resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
- else
- end
- end
-
- def map_collection_actions(map, resource)
- resource.collection_methods.each do |method, actions|
- actions.each do |action|
- [method].flatten.each do |m|
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
- action_path ||= action
-
- map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
- end
- end
- end
- end
-
- def map_default_collection_actions(map, resource)
- index_route_name = "#{resource.name_prefix}#{resource.plural}"
-
- if resource.uncountable?
- index_route_name << "_index"
- end
-
- map_resource_routes(map, resource, :index, resource.path, index_route_name)
- map_resource_routes(map, resource, :create, resource.path, index_route_name)
- end
-
- def map_default_singleton_actions(map, resource)
- map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
- end
-
- def map_new_actions(map, resource)
- resource.new_methods.each do |method, actions|
- actions.each do |action|
- route_path = resource.new_path
- route_name = "new_#{resource.name_prefix}#{resource.singular}"
-
- unless action == :new
- route_path = "#{route_path}#{resource.action_separator}#{action}"
- route_name = "#{action}_#{route_name}"
- end
-
- map_resource_routes(map, resource, action, route_path, route_name, method)
- end
- end
- end
-
- def map_member_actions(map, resource)
- resource.member_methods.each do |method, actions|
- actions.each do |action|
- [method].flatten.each do |m|
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
- action_path ||= ActionController::Base.resources_path_names[action] || action
-
- map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
- end
- end
- end
-
- route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
- map_resource_routes(map, resource, :show, resource.member_path, route_path)
- map_resource_routes(map, resource, :update, resource.member_path, route_path)
- map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
- end
-
- def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
- if resource.has_action?(action)
- action_options = action_options_for(action, resource, method, resource_options)
- formatted_route_path = "#{route_path}.:format"
-
- if route_name && @set.named_routes[route_name.to_sym].nil?
- map.named_route(route_name, formatted_route_path, action_options)
- else
- map.connect(formatted_route_path, action_options)
- end
- end
- end
-
- def add_conditions_for(conditions, method)
- returning({:conditions => conditions.dup}) do |options|
- options[:conditions][:method] = method unless method == :any
- end
- end
-
- def action_options_for(action, resource, method = nil, resource_options = {})
- default_options = { :action => action.to_s }
- require_id = !resource.kind_of?(SingletonResource)
- force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
-
- case default_options[:action]
- when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
- when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
- when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
- when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
- when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
- else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 9e40108d00..a6e46b1c78 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -53,63 +53,6 @@ module ActionDispatch
end
end
- # Mapper instances are used to build routes. The object passed to the draw
- # block in config/routes.rb is a Mapper instance.
- #
- # Mapper instances have relatively few instance methods, in order to avoid
- # clashes with named routes.
- class Mapper #:doc:
- include Routing::Resources
-
- def initialize(set) #:nodoc:
- @set = set
- end
-
- # Create an unnamed route with the provided +path+ and +options+. See
- # ActionDispatch::Routing for an introduction to routes.
- def connect(path, options = {})
- @set.add_route(path, options)
- end
-
- # Creates a named route called "root" for matching the root level request.
- def root(options = {})
- if options.is_a?(Symbol)
- if source_route = @set.named_routes.routes[options]
- options = source_route.defaults.merge({ :conditions => source_route.conditions })
- end
- end
- named_route("root", '', options)
- end
-
- def named_route(name, path, options = {}) #:nodoc:
- @set.add_named_route(name, path, options)
- end
-
- # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
- # Example:
- #
- # map.namespace(:admin) do |admin|
- # admin.resources :products,
- # :has_many => [ :tags, :images, :variants ]
- # end
- #
- # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
- # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
- # Admin::TagsController.
- def namespace(name, options = {}, &block)
- if options[:namespace]
- with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
- else
- with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
- end
- end
-
- def method_missing(route_name, *args, &proc) #:nodoc:
- super unless args.length >= 1 && proc.nil?
- @set.add_named_route(route_name, *args)
- end
- end
-
# A NamedRouteCollection instance is a collection of named routes, and also
# maintains an anonymous module that can be used to install helpers for the
# named routes.
@@ -347,109 +290,14 @@ module ActionDispatch
routes_changed_at
end
- def add_route(path, options = {})
- options = options.dup
-
- if conditions = options.delete(:conditions)
- conditions = conditions.dup
- method = [conditions.delete(:method)].flatten.compact
- method.map! { |m|
- m = m.to_s.upcase
-
- if m == "HEAD"
- raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
- end
-
- unless HTTP_METHODS.include?(m.downcase.to_sym)
- raise ArgumentError, "Invalid HTTP method specified in route conditions"
- end
-
- m
- }
-
- if method.length > 1
- method = Regexp.union(*method)
- elsif method.length == 1
- method = method.first
- else
- method = nil
- end
- end
-
- path_prefix = options.delete(:path_prefix)
- name_prefix = options.delete(:name_prefix)
- namespace = options.delete(:namespace)
-
- name = options.delete(:_name)
- name = "#{name_prefix}#{name}" if name_prefix
-
- requirements = options.delete(:requirements) || {}
- defaults = options.delete(:defaults) || {}
- options.each do |k, v|
- if v.is_a?(Regexp)
- if value = options.delete(k)
- requirements[k.to_sym] = value
- end
- else
- value = options.delete(k)
- defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
- end
- end
-
- requirements.each do |_, requirement|
- if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
- end
- if requirement.multiline?
- raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
- end
- end
-
- possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
- requirements[:controller] ||= Regexp.union(*possible_names)
-
- if defaults[:controller]
- defaults[:action] ||= 'index'
- defaults[:controller] = defaults[:controller].to_s
- defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
- end
-
- if defaults[:action]
- defaults[:action] = defaults[:action].to_s
- end
-
- if path.is_a?(String)
- path = "#{path_prefix}/#{path}" if path_prefix
- path = path.gsub('.:format', '(.:format)')
- path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
- glob = $1.to_sym if path =~ /\/\*(\w+)$/
- path = ::Rack::Mount::Utils.normalize_path(path)
- path = ::Rack::Mount::Strexp.compile(path, requirements, %w( / . ? ))
-
- if glob && !defaults[glob].blank?
- raise ActionController::RoutingError, "paths cannot have non-empty default values"
- end
- end
-
- app = Dispatcher.new(:defaults => defaults, :glob => glob)
-
- conditions = {}
- conditions[:request_method] = method if method
- conditions[:path_info] = path if path
-
+ def add_route(app, conditions = {}, defaults = {}, name = nil)
route = @set.add_route(app, conditions, defaults, name)
route.extend(RouteExtensions)
+ named_routes[name] = route if name
routes << route
route
end
- def add_named_route(name, path, options = {})
- options[:_name] = name
- route = add_route(path, options)
- named_routes[route.name] = route
- route
- end
-
def options_as_params(options)
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
@@ -644,56 +492,6 @@ module ActionDispatch
_escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s
end
end
-
- def optionalize_trailing_dynamic_segments(path, requirements, defaults)
- path = (path =~ /^\//) ? path.dup : "/#{path}"
- optional, segments = true, []
-
- required_segments = requirements.keys
- required_segments -= defaults.keys.compact
-
- old_segments = path.split('/')
- old_segments.shift
- length = old_segments.length
-
- old_segments.reverse.each_with_index do |segment, index|
- required_segments.each do |required|
- if segment =~ /#{required}/
- optional = false
- break
- end
- end
-
- if optional
- if segment == ":id" && segments.include?(":action")
- optional = false
- elsif segment == ":controller" || segment == ":action" || segment == ":id"
- # Ignore
- elsif !(segment =~ /^:\w+$/) &&
- !(segment =~ /^:\w+\(\.:format\)$/)
- optional = false
- elsif segment =~ /^:(\w+)$/
- if defaults.has_key?($1.to_sym)
- defaults.delete($1.to_sym)
- else
- optional = false
- end
- end
- end
-
- if optional && index < length - 1
- segments.unshift('(/', segment)
- segments.push(')')
- elsif optional
- segments.unshift('/(', segment)
- segments.push(')')
- else
- segments.unshift('/', segment)
- end
- end
-
- segments.join
- end
end
end
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 4f1bafbad1..92373b5d26 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -41,7 +41,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_should_arrange_actions
- resource = ActionDispatch::Routing::Resources::Resource.new(:messages,
+ resource = ActionDispatch::Routing::Mapper::Resource.new(:messages,
:collection => { :rss => :get, :reorder => :post, :csv => :post },
:member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post },
:new => { :preview => :get, :draft => :get })
@@ -54,18 +54,18 @@ class ResourcesTest < ActionController::TestCase
end
def test_should_resource_controller_name_equal_resource_name_by_default
- resource = ActionDispatch::Routing::Resources::Resource.new(:messages, {})
+ resource = ActionDispatch::Routing::Mapper::Resource.new(:messages, {})
assert_equal 'messages', resource.controller
end
def test_should_resource_controller_name_equal_controller_option
- resource = ActionDispatch::Routing::Resources::Resource.new(:messages, :controller => 'posts')
+ resource = ActionDispatch::Routing::Mapper::Resource.new(:messages, :controller => 'posts')
assert_equal 'posts', resource.controller
end
def test_should_all_singleton_paths_be_the_same
[ :path, :nesting_path_prefix, :member_path ].each do |method|
- resource = ActionDispatch::Routing::Resources::SingletonResource.new(:messages, :path_prefix => 'admin')
+ resource = ActionDispatch::Routing::Mapper::SingletonResource.new(:messages, :path_prefix => 'admin')
assert_equal 'admin/messages', resource.send(method)
end
end
@@ -121,7 +121,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_override_paths_for_default_restful_actions
- resource = ActionDispatch::Routing::Resources::Resource.new(:messages,
+ resource = ActionDispatch::Routing::Mapper::Resource.new(:messages,
:path_names => {:new => 'nuevo', :edit => 'editar'})
assert_equal resource.new_path, "#{resource.path}/nuevo"
end
--
cgit v1.2.3
From a1ce52effccae4851593f1d9b83ca9bf826bf338 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 20 Oct 2009 12:31:23 -0500
Subject: New routing dsl
---
actionpack/lib/action_dispatch/routing.rb | 1 +
.../action_dispatch/routing/deprecated_mapper.rb | 879 +++++++++++++++++
actionpack/lib/action_dispatch/routing/mapper.rb | 1004 +++++---------------
.../lib/action_dispatch/routing/route_set.rb | 4 +-
actionpack/test/controller/resources_test.rb | 20 +-
actionpack/test/controller/routing_test.rb | 332 +++++++
6 files changed, 1443 insertions(+), 797 deletions(-)
create mode 100644 actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 0647d051cb..3803929847 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -260,6 +260,7 @@ module ActionDispatch
# Run rake routes.
#
module Routing
+ autoload :DeprecatedMapper, 'action_dispatch/routing/deprecated_mapper'
autoload :Mapper, 'action_dispatch/routing/mapper'
autoload :RouteSet, 'action_dispatch/routing/route_set'
diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
new file mode 100644
index 0000000000..f2a1f10fa7
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
@@ -0,0 +1,879 @@
+module ActionDispatch
+ module Routing
+ # Mapper instances are used to build routes. The object passed to the draw
+ # block in config/routes.rb is a Mapper instance.
+ #
+ # Mapper instances have relatively few instance methods, in order to avoid
+ # clashes with named routes.
+ #
+ # == Overview
+ #
+ # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
+ # is something that can be pointed at and it will respond with a representation of the data requested.
+ # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
+ # requests XML data.
+ #
+ # RESTful design is based on the assumption that there are four generic verbs that a user of an
+ # application can request from a \resource (the noun).
+ #
+ # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
+ # denotes the type of action that should take place.
+ #
+ # === The Different Methods and their Usage
+ #
+ # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
+ # * POST - Creation of \resources.
+ # * PUT - Editing of attributes on a \resource.
+ # * DELETE - Deletion of a \resource.
+ #
+ # === Examples
+ #
+ # # A GET request on the Posts resource is asking for all Posts
+ # GET /posts
+ #
+ # # A GET request on a single Post resource is asking for that particular Post
+ # GET /posts/1
+ #
+ # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
+ # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
+ #
+ # # A PUT request on a single Post resource is asking for a Post to be updated
+ # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
+ #
+ # # A DELETE request on a single Post resource is asking for it to be deleted
+ # DELETE /posts # with => { :id => 1 }
+ #
+ # By using the REST convention, users of our application can assume certain things about how the data
+ # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
+ # supplying you with methods to create them in your routes.rb file.
+ #
+ # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
+ class DeprecatedMapper #:doc:
+ def initialize(set) #:nodoc:
+ @set = set
+ end
+
+ # Create an unnamed route with the provided +path+ and +options+. See
+ # ActionDispatch::Routing for an introduction to routes.
+ def connect(path, options = {})
+ options = options.dup
+
+ if conditions = options.delete(:conditions)
+ conditions = conditions.dup
+ method = [conditions.delete(:method)].flatten.compact
+ method.map! { |m|
+ m = m.to_s.upcase
+
+ if m == "HEAD"
+ raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
+ end
+
+ unless HTTP_METHODS.include?(m.downcase.to_sym)
+ raise ArgumentError, "Invalid HTTP method specified in route conditions"
+ end
+
+ m
+ }
+
+ if method.length > 1
+ method = Regexp.union(*method)
+ elsif method.length == 1
+ method = method.first
+ else
+ method = nil
+ end
+ end
+
+ path_prefix = options.delete(:path_prefix)
+ name_prefix = options.delete(:name_prefix)
+ namespace = options.delete(:namespace)
+
+ name = options.delete(:_name)
+ name = "#{name_prefix}#{name}" if name_prefix
+
+ requirements = options.delete(:requirements) || {}
+ defaults = options.delete(:defaults) || {}
+ options.each do |k, v|
+ if v.is_a?(Regexp)
+ if value = options.delete(k)
+ requirements[k.to_sym] = value
+ end
+ else
+ value = options.delete(k)
+ defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
+ end
+ end
+
+ requirements.each do |_, requirement|
+ if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
+ end
+ if requirement.multiline?
+ raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
+ end
+ end
+
+ possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
+ requirements[:controller] ||= Regexp.union(*possible_names)
+
+ if defaults[:controller]
+ defaults[:action] ||= 'index'
+ defaults[:controller] = defaults[:controller].to_s
+ defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
+ end
+
+ if defaults[:action]
+ defaults[:action] = defaults[:action].to_s
+ end
+
+ if path.is_a?(String)
+ path = "#{path_prefix}/#{path}" if path_prefix
+ path = path.gsub('.:format', '(.:format)')
+ path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
+ glob = $1.to_sym if path =~ /\/\*(\w+)$/
+ path = ::Rack::Mount::Utils.normalize_path(path)
+ path = ::Rack::Mount::Strexp.compile(path, requirements, %w( / . ? ))
+
+ if glob && !defaults[glob].blank?
+ raise ActionController::RoutingError, "paths cannot have non-empty default values"
+ end
+ end
+
+ app = Routing::RouteSet::Dispatcher.new(:defaults => defaults, :glob => glob)
+
+ conditions = {}
+ conditions[:request_method] = method if method
+ conditions[:path_info] = path if path
+
+ @set.add_route(app, conditions, defaults, name)
+ end
+
+ def optionalize_trailing_dynamic_segments(path, requirements, defaults) #:nodoc:
+ path = (path =~ /^\//) ? path.dup : "/#{path}"
+ optional, segments = true, []
+
+ required_segments = requirements.keys
+ required_segments -= defaults.keys.compact
+
+ old_segments = path.split('/')
+ old_segments.shift
+ length = old_segments.length
+
+ old_segments.reverse.each_with_index do |segment, index|
+ required_segments.each do |required|
+ if segment =~ /#{required}/
+ optional = false
+ break
+ end
+ end
+
+ if optional
+ if segment == ":id" && segments.include?(":action")
+ optional = false
+ elsif segment == ":controller" || segment == ":action" || segment == ":id"
+ # Ignore
+ elsif !(segment =~ /^:\w+$/) &&
+ !(segment =~ /^:\w+\(\.:format\)$/)
+ optional = false
+ elsif segment =~ /^:(\w+)$/
+ if defaults.has_key?($1.to_sym)
+ defaults.delete($1.to_sym)
+ else
+ optional = false
+ end
+ end
+ end
+
+ if optional && index < length - 1
+ segments.unshift('(/', segment)
+ segments.push(')')
+ elsif optional
+ segments.unshift('/(', segment)
+ segments.push(')')
+ else
+ segments.unshift('/', segment)
+ end
+ end
+
+ segments.join
+ end
+ private :optionalize_trailing_dynamic_segments
+
+ # Creates a named route called "root" for matching the root level request.
+ def root(options = {})
+ if options.is_a?(Symbol)
+ if source_route = @set.named_routes.routes[options]
+ options = source_route.defaults.merge({ :conditions => source_route.conditions })
+ end
+ end
+ named_route("root", '', options)
+ end
+
+ def named_route(name, path, options = {}) #:nodoc:
+ options[:_name] = name
+ connect(path, options)
+ end
+
+ # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
+ # Example:
+ #
+ # map.namespace(:admin) do |admin|
+ # admin.resources :products,
+ # :has_many => [ :tags, :images, :variants ]
+ # end
+ #
+ # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
+ # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
+ # Admin::TagsController.
+ def namespace(name, options = {}, &block)
+ if options[:namespace]
+ with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
+ else
+ with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
+ end
+ end
+
+ def method_missing(route_name, *args, &proc) #:nodoc:
+ super unless args.length >= 1 && proc.nil?
+ named_route(route_name, *args)
+ end
+
+ INHERITABLE_OPTIONS = :namespace, :shallow
+
+ class Resource #:nodoc:
+ DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
+
+ attr_reader :collection_methods, :member_methods, :new_methods
+ attr_reader :path_prefix, :name_prefix, :path_segment
+ attr_reader :plural, :singular
+ attr_reader :options
+
+ def initialize(entities, options)
+ @plural ||= entities
+ @singular ||= options[:singular] || plural.to_s.singularize
+ @path_segment = options.delete(:as) || @plural
+
+ @options = options
+
+ arrange_actions
+ add_default_actions
+ set_allowed_actions
+ set_prefixes
+ end
+
+ def controller
+ @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
+ end
+
+ def requirements(with_id = false)
+ @requirements ||= @options[:requirements] || {}
+ @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
+
+ with_id ? @requirements.merge(@id_requirement) : @requirements
+ end
+
+ def conditions
+ @conditions ||= @options[:conditions] || {}
+ end
+
+ def path
+ @path ||= "#{path_prefix}/#{path_segment}"
+ end
+
+ def new_path
+ new_action = self.options[:path_names][:new] if self.options[:path_names]
+ new_action ||= ActionController::Base.resources_path_names[:new]
+ @new_path ||= "#{path}/#{new_action}"
+ end
+
+ def shallow_path_prefix
+ @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
+ end
+
+ def member_path
+ @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
+ end
+
+ def nesting_path_prefix
+ @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
+ end
+
+ def shallow_name_prefix
+ @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
+ end
+
+ def nesting_name_prefix
+ "#{shallow_name_prefix}#{singular}_"
+ end
+
+ def action_separator
+ @action_separator ||= ActionController::Base.resource_action_separator
+ end
+
+ def uncountable?
+ @singular.to_s == @plural.to_s
+ end
+
+ def has_action?(action)
+ !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
+ end
+
+ protected
+ def arrange_actions
+ @collection_methods = arrange_actions_by_methods(options.delete(:collection))
+ @member_methods = arrange_actions_by_methods(options.delete(:member))
+ @new_methods = arrange_actions_by_methods(options.delete(:new))
+ end
+
+ def add_default_actions
+ add_default_action(member_methods, :get, :edit)
+ add_default_action(new_methods, :get, :new)
+ end
+
+ def set_allowed_actions
+ only, except = @options.values_at(:only, :except)
+ @allowed_actions ||= {}
+
+ if only == :all || except == :none
+ only = nil
+ except = []
+ elsif only == :none || except == :all
+ only = []
+ except = nil
+ end
+
+ if only
+ @allowed_actions[:only] = Array(only).map {|a| a.to_sym }
+ elsif except
+ @allowed_actions[:except] = Array(except).map {|a| a.to_sym }
+ end
+ end
+
+ def action_allowed?(action)
+ only, except = @allowed_actions.values_at(:only, :except)
+ (!only || only.include?(action)) && (!except || !except.include?(action))
+ end
+
+ def set_prefixes
+ @path_prefix = options.delete(:path_prefix)
+ @name_prefix = options.delete(:name_prefix)
+ end
+
+ def arrange_actions_by_methods(actions)
+ (actions || {}).inject({}) do |flipped_hash, (key, value)|
+ (flipped_hash[value] ||= []) << key
+ flipped_hash
+ end
+ end
+
+ def add_default_action(collection, method, action)
+ (collection[method] ||= []).unshift(action)
+ end
+ end
+
+ class SingletonResource < Resource #:nodoc:
+ def initialize(entity, options)
+ @singular = @plural = entity
+ options[:controller] ||= @singular.to_s.pluralize
+ super
+ end
+
+ alias_method :shallow_path_prefix, :path_prefix
+ alias_method :shallow_name_prefix, :name_prefix
+ alias_method :member_path, :path
+ alias_method :nesting_path_prefix, :path
+ end
+
+ # Creates named routes for implementing verb-oriented controllers
+ # for a collection \resource.
+ #
+ # For example:
+ #
+ # map.resources :messages
+ #
+ # will map the following actions in the corresponding controller:
+ #
+ # class MessagesController < ActionController::Base
+ # # GET messages_url
+ # def index
+ # # return all messages
+ # end
+ #
+ # # GET new_message_url
+ # def new
+ # # return an HTML form for describing a new message
+ # end
+ #
+ # # POST messages_url
+ # def create
+ # # create a new message
+ # end
+ #
+ # # GET message_url(:id => 1)
+ # def show
+ # # find and return a specific message
+ # end
+ #
+ # # GET edit_message_url(:id => 1)
+ # def edit
+ # # return an HTML form for editing a specific message
+ # end
+ #
+ # # PUT message_url(:id => 1)
+ # def update
+ # # find and update a specific message
+ # end
+ #
+ # # DELETE message_url(:id => 1)
+ # def destroy
+ # # delete a specific message
+ # end
+ # end
+ #
+ # Along with the routes themselves, +resources+ generates named routes for use in
+ # controllers and views. map.resources :messages produces the following named routes and helpers:
+ #
+ # Named Route Helpers
+ # ============ =====================================================
+ # messages messages_url, hash_for_messages_url,
+ # messages_path, hash_for_messages_path
+ #
+ # message message_url(id), hash_for_message_url(id),
+ # message_path(id), hash_for_message_path(id)
+ #
+ # new_message new_message_url, hash_for_new_message_url,
+ # new_message_path, hash_for_new_message_path
+ #
+ # edit_message edit_message_url(id), hash_for_edit_message_url(id),
+ # edit_message_path(id), hash_for_edit_message_path(id)
+ #
+ # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
+ #
+ # redirect_to :controller => 'messages', :action => 'index'
+ # # and
+ # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
+ #
+ # now become:
+ #
+ # redirect_to messages_url
+ # # and
+ # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
+ #
+ # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
+ # form tags. The form helpers make this a little easier. For an update form with a @message object:
+ #
+ # <%= form_tag message_path(@message), :method => :put %>
+ #
+ # or
+ #
+ # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
+ #
+ # or
+ #
+ # <% form_for @message do |f| %>
+ #
+ # which takes into account whether @message is a new record or not and generates the
+ # path and method accordingly.
+ #
+ # The +resources+ method accepts the following options to customize the resulting routes:
+ # * :collection - Add named routes for other actions that operate on the collection.
+ # Takes a hash of #{action} => #{method}, where method is :get/:post/:put/:delete,
+ # an array of any of the previous, or :any if the method does not matter.
+ # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
+ # * :member - Same as :collection, but for actions that operate on a specific member.
+ # * :new - Same as :collection, but for actions that operate on the new \resource action.
+ # * :controller - Specify the controller name for the routes.
+ # * :singular - Specify the singular name used in the member routes.
+ # * :requirements - Set custom routing parameter requirements; this is a hash of either
+ # regular expressions (which must match for the route to match) or extra parameters. For example:
+ #
+ # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
+ #
+ # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
+ # * :conditions - Specify custom routing recognition conditions. \Resources sets the :method value for the method-specific routes.
+ # * :as - Specify a different \resource name to use in the URL path. For example:
+ # # products_path == '/productos'
+ # map.resources :products, :as => 'productos' do |product|
+ # # product_reviews_path(product) == '/productos/1234/comentarios'
+ # product.resources :product_reviews, :as => 'comentarios'
+ # end
+ #
+ # * :has_one - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
+ # * :has_many - Same has :has_one, but for plural \resources.
+ #
+ # You may directly specify the routing association with +has_one+ and +has_many+ like:
+ #
+ # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
+ #
+ # This is the same as:
+ #
+ # map.resources :notes do |notes|
+ # notes.resource :author
+ # notes.resources :comments
+ # notes.resources :attachments
+ # end
+ #
+ # * :path_names - Specify different path names for the actions. For example:
+ # # new_products_path == '/productos/nuevo'
+ # # bids_product_path(1) == '/productos/1/licitacoes'
+ # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
+ #
+ # You can also set default action names from an environment, like this:
+ # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
+ #
+ # * :path_prefix - Set a prefix to the routes with required route variables.
+ #
+ # Weblog comments usually belong to a post, so you might use +resources+ like:
+ #
+ # map.resources :articles
+ # map.resources :comments, :path_prefix => '/articles/:article_id'
+ #
+ # You can nest +resources+ calls to set this automatically:
+ #
+ # map.resources :articles do |article|
+ # article.resources :comments
+ # end
+ #
+ # The comment \resources work the same, but must now include a value for :article_id.
+ #
+ # article_comments_url(@article)
+ # article_comment_url(@article, @comment)
+ #
+ # article_comments_url(:article_id => @article)
+ # article_comment_url(:article_id => @article, :id => @comment)
+ #
+ # If you don't want to load all objects from the database you might want to use the article_id directly:
+ #
+ # articles_comments_url(@comment.article_id, @comment)
+ #
+ # * :name_prefix - Define a prefix for all generated routes, usually ending in an underscore.
+ # Use this if you have named routes that may clash.
+ #
+ # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
+ # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
+ #
+ # You may also use :name_prefix to override the generic named routes in a nested \resource:
+ #
+ # map.resources :articles do |article|
+ # article.resources :comments, :name_prefix => nil
+ # end
+ #
+ # This will yield named \resources like so:
+ #
+ # comments_url(@article)
+ # comment_url(@article, @comment)
+ #
+ # * :shallow - If true, paths for nested resources which reference a specific member
+ # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
+ #
+ # The :shallow option is inherited by any nested resource(s).
+ #
+ # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
+ #
+ # map.resources :users, :shallow => true do |user|
+ # user.resources :posts do |post|
+ # post.resources :comments
+ # end
+ # end
+ # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
+ # # also adds the usual named route called "user_posts"
+ # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
+ # # also adds the named route called "post"
+ # # --> GET /posts/2/comments (maps to the CommentsController#index action)
+ # # also adds the named route called "post_comments"
+ # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
+ # # also adds the named route called "comment"
+ #
+ # You may also use :shallow in combination with the +has_one+ and +has_many+ shorthand notations like:
+ #
+ # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
+ #
+ # * :only and :except - Specify which of the seven default actions should be routed to.
+ #
+ # :only and :except may be set to :all, :none, an action name or a
+ # list of action names. By default, routes are generated for all seven actions.
+ #
+ # For example:
+ #
+ # map.resources :posts, :only => [:index, :show] do |post|
+ # post.resources :comments, :except => [:update, :destroy]
+ # end
+ # # --> GET /posts (maps to the PostsController#index action)
+ # # --> POST /posts (fails)
+ # # --> GET /posts/1 (maps to the PostsController#show action)
+ # # --> DELETE /posts/1 (fails)
+ # # --> POST /posts/1/comments (maps to the CommentsController#create action)
+ # # --> PUT /posts/1/comments/1 (fails)
+ #
+ # If map.resources is called with multiple resources, they all get the same options applied.
+ #
+ # Examples:
+ #
+ # map.resources :messages, :path_prefix => "/thread/:thread_id"
+ # # --> GET /thread/7/messages/1
+ #
+ # map.resources :messages, :collection => { :rss => :get }
+ # # --> GET /messages/rss (maps to the #rss action)
+ # # also adds a named route called "rss_messages"
+ #
+ # map.resources :messages, :member => { :mark => :post }
+ # # --> POST /messages/1/mark (maps to the #mark action)
+ # # also adds a named route called "mark_message"
+ #
+ # map.resources :messages, :new => { :preview => :post }
+ # # --> POST /messages/new/preview (maps to the #preview action)
+ # # also adds a named route called "preview_new_message"
+ #
+ # map.resources :messages, :new => { :new => :any, :preview => :post }
+ # # --> POST /messages/new/preview (maps to the #preview action)
+ # # also adds a named route called "preview_new_message"
+ # # --> /messages/new can be invoked via any request method
+ #
+ # map.resources :messages, :controller => "categories",
+ # :path_prefix => "/category/:category_id",
+ # :name_prefix => "category_"
+ # # --> GET /categories/7/messages/1
+ # # has named route "category_message"
+ #
+ # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
+ # HTTP POST on new_message_url will raise a RoutingError exception. The default route in
+ # config/routes.rb overrides this and allows invalid HTTP methods for \resource routes.
+ def resources(*entities, &block)
+ options = entities.extract_options!
+ entities.each { |entity| map_resource(entity, options.dup, &block) }
+ end
+
+ # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
+ # A singleton \resource is global to its current context. For unnested singleton \resources,
+ # the \resource is global to the current user visiting the application, such as a user's
+ # /account profile. For nested singleton \resources, the \resource is global to its parent
+ # \resource, such as a projects \resource that has_one :project_manager.
+ # The project_manager should be mapped as a singleton \resource under projects:
+ #
+ # map.resources :projects do |project|
+ # project.resource :project_manager
+ # end
+ #
+ # See +resources+ for general conventions. These are the main differences:
+ # * A singular name is given to map.resource. The default controller name is still taken from the plural name.
+ # * To specify a custom plural name, use the :plural option. There is no :singular option.
+ # * No default index route is created for the singleton \resource controller.
+ # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
+ #
+ # For example:
+ #
+ # map.resource :account
+ #
+ # maps these actions in the Accounts controller:
+ #
+ # class AccountsController < ActionController::Base
+ # # GET new_account_url
+ # def new
+ # # return an HTML form for describing the new account
+ # end
+ #
+ # # POST account_url
+ # def create
+ # # create an account
+ # end
+ #
+ # # GET account_url
+ # def show
+ # # find and return the account
+ # end
+ #
+ # # GET edit_account_url
+ # def edit
+ # # return an HTML form for editing the account
+ # end
+ #
+ # # PUT account_url
+ # def update
+ # # find and update the account
+ # end
+ #
+ # # DELETE account_url
+ # def destroy
+ # # delete the account
+ # end
+ # end
+ #
+ # Along with the routes themselves, +resource+ generates named routes for
+ # use in controllers and views. map.resource :account produces
+ # these named routes and helpers:
+ #
+ # Named Route Helpers
+ # ============ =============================================
+ # account account_url, hash_for_account_url,
+ # account_path, hash_for_account_path
+ #
+ # new_account new_account_url, hash_for_new_account_url,
+ # new_account_path, hash_for_new_account_path
+ #
+ # edit_account edit_account_url, hash_for_edit_account_url,
+ # edit_account_path, hash_for_edit_account_path
+ def resource(*entities, &block)
+ options = entities.extract_options!
+ entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
+ end
+
+ private
+ def map_resource(entities, options = {}, &block)
+ resource = Resource.new(entities, options)
+
+ with_options :controller => resource.controller do |map|
+ map_associations(resource, options)
+
+ if block_given?
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
+ end
+
+ map_collection_actions(map, resource)
+ map_default_collection_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
+ end
+ end
+
+ def map_singleton_resource(entities, options = {}, &block)
+ resource = SingletonResource.new(entities, options)
+
+ with_options :controller => resource.controller do |map|
+ map_associations(resource, options)
+
+ if block_given?
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
+ end
+
+ map_collection_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
+ map_default_singleton_actions(map, resource)
+ end
+ end
+
+ def map_associations(resource, options)
+ map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
+
+ path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
+ name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
+
+ Array(options[:has_one]).each do |association|
+ resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
+ end
+ end
+
+ def map_has_many_associations(resource, associations, options)
+ case associations
+ when Hash
+ associations.each do |association,has_many|
+ map_has_many_associations(resource, association, options.merge(:has_many => has_many))
+ end
+ when Array
+ associations.each do |association|
+ map_has_many_associations(resource, association, options)
+ end
+ when Symbol, String
+ resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
+ else
+ end
+ end
+
+ def map_collection_actions(map, resource)
+ resource.collection_methods.each do |method, actions|
+ actions.each do |action|
+ [method].flatten.each do |m|
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
+ action_path ||= action
+
+ map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
+ end
+ end
+ end
+ end
+
+ def map_default_collection_actions(map, resource)
+ index_route_name = "#{resource.name_prefix}#{resource.plural}"
+
+ if resource.uncountable?
+ index_route_name << "_index"
+ end
+
+ map_resource_routes(map, resource, :index, resource.path, index_route_name)
+ map_resource_routes(map, resource, :create, resource.path, index_route_name)
+ end
+
+ def map_default_singleton_actions(map, resource)
+ map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
+ end
+
+ def map_new_actions(map, resource)
+ resource.new_methods.each do |method, actions|
+ actions.each do |action|
+ route_path = resource.new_path
+ route_name = "new_#{resource.name_prefix}#{resource.singular}"
+
+ unless action == :new
+ route_path = "#{route_path}#{resource.action_separator}#{action}"
+ route_name = "#{action}_#{route_name}"
+ end
+
+ map_resource_routes(map, resource, action, route_path, route_name, method)
+ end
+ end
+ end
+
+ def map_member_actions(map, resource)
+ resource.member_methods.each do |method, actions|
+ actions.each do |action|
+ [method].flatten.each do |m|
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
+ action_path ||= ActionController::Base.resources_path_names[action] || action
+
+ map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
+ end
+ end
+ end
+
+ route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
+ map_resource_routes(map, resource, :show, resource.member_path, route_path)
+ map_resource_routes(map, resource, :update, resource.member_path, route_path)
+ map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
+ end
+
+ def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
+ if resource.has_action?(action)
+ action_options = action_options_for(action, resource, method, resource_options)
+ formatted_route_path = "#{route_path}.:format"
+
+ if route_name && @set.named_routes[route_name.to_sym].nil?
+ map.named_route(route_name, formatted_route_path, action_options)
+ else
+ map.connect(formatted_route_path, action_options)
+ end
+ end
+ end
+
+ def add_conditions_for(conditions, method)
+ returning({:conditions => conditions.dup}) do |options|
+ options[:conditions][:method] = method unless method == :any
+ end
+ end
+
+ def action_options_for(action, resource, method = nil, resource_options = {})
+ default_options = { :action => action.to_s }
+ require_id = !resource.kind_of?(SingletonResource)
+ force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
+
+ case default_options[:action]
+ when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
+ when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
+ when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
+ when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
+ when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
+ else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 44afbb9cd7..ab4193266a 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,878 +1,312 @@
module ActionDispatch
module Routing
- # Mapper instances are used to build routes. The object passed to the draw
- # block in config/routes.rb is a Mapper instance.
- #
- # Mapper instances have relatively few instance methods, in order to avoid
- # clashes with named routes.
- #
- # == Overview
- #
- # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
- # is something that can be pointed at and it will respond with a representation of the data requested.
- # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
- # requests XML data.
- #
- # RESTful design is based on the assumption that there are four generic verbs that a user of an
- # application can request from a \resource (the noun).
- #
- # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
- # denotes the type of action that should take place.
- #
- # === The Different Methods and their Usage
- #
- # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
- # * POST - Creation of \resources.
- # * PUT - Editing of attributes on a \resource.
- # * DELETE - Deletion of a \resource.
- #
- # === Examples
- #
- # # A GET request on the Posts resource is asking for all Posts
- # GET /posts
- #
- # # A GET request on a single Post resource is asking for that particular Post
- # GET /posts/1
- #
- # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
- # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
- #
- # # A PUT request on a single Post resource is asking for a Post to be updated
- # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
- #
- # # A DELETE request on a single Post resource is asking for it to be deleted
- # DELETE /posts # with => { :id => 1 }
- #
- # By using the REST convention, users of our application can assume certain things about how the data
- # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
- # supplying you with methods to create them in your routes.rb file.
- #
- # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
- class Mapper #:doc:
- def initialize(set) #:nodoc:
- @set = set
- end
-
- # Create an unnamed route with the provided +path+ and +options+. See
- # ActionDispatch::Routing for an introduction to routes.
- def connect(path, options = {})
- options = options.dup
-
- if conditions = options.delete(:conditions)
- conditions = conditions.dup
- method = [conditions.delete(:method)].flatten.compact
- method.map! { |m|
- m = m.to_s.upcase
+ class Mapper
+ module Resources
+ def resource(*resources, &block)
+ options = resources.last.is_a?(Hash) ? resources.pop : {}
+
+ if resources.length > 1
+ raise ArgumentError if block_given?
+ resources.each { |r| resource(r, options) }
+ return self
+ end
- if m == "HEAD"
- raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
- end
+ resource = resources.pop
- unless HTTP_METHODS.include?(m.downcase.to_sym)
- raise ArgumentError, "Invalid HTTP method specified in route conditions"
+ if @scope[:scope_level] == :resources
+ member do
+ resource(resource, options, &block)
end
-
- m
- }
-
- if method.length > 1
- method = Regexp.union(*method)
- elsif method.length == 1
- method = method.first
- else
- method = nil
+ return self
end
- end
- path_prefix = options.delete(:path_prefix)
- name_prefix = options.delete(:name_prefix)
- namespace = options.delete(:namespace)
+ controller(resource) do
+ namespace(resource) do
+ with_scope_level(:resource) do
+ yield if block_given?
- name = options.delete(:_name)
- name = "#{name_prefix}#{name}" if name_prefix
-
- requirements = options.delete(:requirements) || {}
- defaults = options.delete(:defaults) || {}
- options.each do |k, v|
- if v.is_a?(Regexp)
- if value = options.delete(k)
- requirements[k.to_sym] = value
+ get "", :to => :show
+ post "", :to => :create
+ put "", :to => :update
+ delete "", :to => :destory
+ end
end
- else
- value = options.delete(k)
- defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
end
- end
- requirements.each do |_, requirement|
- if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
- end
- if requirement.multiline?
- raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
- end
+ self
end
- possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
- requirements[:controller] ||= Regexp.union(*possible_names)
+ def resources(*resources, &block)
+ options = resources.last.is_a?(Hash) ? resources.pop : {}
- if defaults[:controller]
- defaults[:action] ||= 'index'
- defaults[:controller] = defaults[:controller].to_s
- defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
- end
-
- if defaults[:action]
- defaults[:action] = defaults[:action].to_s
- end
-
- if path.is_a?(String)
- path = "#{path_prefix}/#{path}" if path_prefix
- path = path.gsub('.:format', '(.:format)')
- path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
- glob = $1.to_sym if path =~ /\/\*(\w+)$/
- path = ::Rack::Mount::Utils.normalize_path(path)
- path = ::Rack::Mount::Strexp.compile(path, requirements, %w( / . ? ))
-
- if glob && !defaults[glob].blank?
- raise ActionController::RoutingError, "paths cannot have non-empty default values"
+ if resources.length > 1
+ raise ArgumentError if block_given?
+ resources.each { |r| resources(r, options) }
+ return self
end
- end
-
- app = Routing::RouteSet::Dispatcher.new(:defaults => defaults, :glob => glob)
- conditions = {}
- conditions[:request_method] = method if method
- conditions[:path_info] = path if path
-
- @set.add_route(app, conditions, defaults, name)
- end
+ resource = resources.pop
- def optionalize_trailing_dynamic_segments(path, requirements, defaults) #:nodoc:
- path = (path =~ /^\//) ? path.dup : "/#{path}"
- optional, segments = true, []
-
- required_segments = requirements.keys
- required_segments -= defaults.keys.compact
-
- old_segments = path.split('/')
- old_segments.shift
- length = old_segments.length
-
- old_segments.reverse.each_with_index do |segment, index|
- required_segments.each do |required|
- if segment =~ /#{required}/
- optional = false
- break
+ if @scope[:scope_level] == :resources
+ member do
+ resources(resource, options, &block)
end
+ return self
end
- if optional
- if segment == ":id" && segments.include?(":action")
- optional = false
- elsif segment == ":controller" || segment == ":action" || segment == ":id"
- # Ignore
- elsif !(segment =~ /^:\w+$/) &&
- !(segment =~ /^:\w+\(\.:format\)$/)
- optional = false
- elsif segment =~ /^:(\w+)$/
- if defaults.has_key?($1.to_sym)
- defaults.delete($1.to_sym)
- else
- optional = false
+ controller(resource) do
+ namespace(resource) do
+ with_scope_level(:resources) do
+ yield if block_given?
+
+ member do
+ get "", :to => :show
+ put "", :to => :update
+ delete "", :to => :destory
+ get "edit", :to => :edit
+ end
+
+ collection do
+ get "", :to => :index
+ post "", :to => :create
+ get "new", :to => :new
+ end
end
end
end
- if optional && index < length - 1
- segments.unshift('(/', segment)
- segments.push(')')
- elsif optional
- segments.unshift('/(', segment)
- segments.push(')')
- else
- segments.unshift('/', segment)
- end
+ self
end
- segments.join
- end
- private :optionalize_trailing_dynamic_segments
-
- # Creates a named route called "root" for matching the root level request.
- def root(options = {})
- if options.is_a?(Symbol)
- if source_route = @set.named_routes.routes[options]
- options = source_route.defaults.merge({ :conditions => source_route.conditions })
+ def collection
+ unless @scope[:scope_level] == :resources
+ raise ArgumentError, "can't use collection outside resources scope"
end
- end
- named_route("root", '', options)
- end
-
- def named_route(name, path, options = {}) #:nodoc:
- options[:_name] = name
- connect(path, options)
- end
- # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
- # Example:
- #
- # map.namespace(:admin) do |admin|
- # admin.resources :products,
- # :has_many => [ :tags, :images, :variants ]
- # end
- #
- # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
- # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
- # Admin::TagsController.
- def namespace(name, options = {}, &block)
- if options[:namespace]
- with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
- else
- with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
+ with_scope_level(:collection) do
+ yield
+ end
end
- end
-
- def method_missing(route_name, *args, &proc) #:nodoc:
- super unless args.length >= 1 && proc.nil?
- named_route(route_name, *args)
- end
-
- INHERITABLE_OPTIONS = :namespace, :shallow
- class Resource #:nodoc:
- DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
-
- attr_reader :collection_methods, :member_methods, :new_methods
- attr_reader :path_prefix, :name_prefix, :path_segment
- attr_reader :plural, :singular
- attr_reader :options
-
- def initialize(entities, options)
- @plural ||= entities
- @singular ||= options[:singular] || plural.to_s.singularize
- @path_segment = options.delete(:as) || @plural
-
- @options = options
-
- arrange_actions
- add_default_actions
- set_allowed_actions
- set_prefixes
- end
+ def member
+ unless @scope[:scope_level] == :resources
+ raise ArgumentError, "can't use member outside resources scope"
+ end
- def controller
- @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
+ with_scope_level(:member) do
+ scope(":id") do
+ yield
+ end
+ end
end
- def requirements(with_id = false)
- @requirements ||= @options[:requirements] || {}
- @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
+ def match(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ args.push(options)
- with_id ? @requirements.merge(@id_requirement) : @requirements
- end
+ case options.delete(:on)
+ when :collection
+ return collection { match(*args) }
+ when :member
+ return member { match(*args) }
+ end
- def conditions
- @conditions ||= @options[:conditions] || {}
- end
+ if @scope[:scope_level] == :resources
+ raise ArgumentError, "can't define route directly in resources scope"
+ end
- def path
- @path ||= "#{path_prefix}/#{path_segment}"
+ super
end
- def new_path
- new_action = self.options[:path_names][:new] if self.options[:path_names]
- new_action ||= ActionController::Base.resources_path_names[:new]
- @new_path ||= "#{path}/#{new_action}"
- end
+ private
+ def with_scope_level(kind)
+ old, @scope[:scope_level] = @scope[:scope_level], kind
+ yield
+ ensure
+ @scope[:scope_level] = old
+ end
+ end
- def shallow_path_prefix
- @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
- end
+ module Scoping
+ def scope(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
- def member_path
- @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
- end
+ constraints = options.delete(:constraints) || {}
+ unless constraints.is_a?(Hash)
+ block, constraints = constraints, {}
+ end
+ constraints, @scope[:constraints] = @scope[:constraints], (@scope[:constraints] || {}).merge(constraints)
+ blocks, @scope[:blocks] = @scope[:blocks], (@scope[:blocks] || []) + [block]
+
+ options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options)
+
+ path_set = controller_set = false
+
+ case args.first
+ when String
+ path_set = true
+ path = args.first
+ path, @scope[:path] = @scope[:path], "#{@scope[:path]}#{Rack::Mount::Utils.normalize_path(path)}"
+ when Symbol
+ controller_set = true
+ controller = args.first
+ controller, @scope[:controller] = @scope[:controller], controller
+ end
- def nesting_path_prefix
- @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
- end
+ yield
- def shallow_name_prefix
- @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
+ self
+ ensure
+ @scope[:path] = path if path_set
+ @scope[:controller] = controller if controller_set
+ @scope[:options] = options
+ @scope[:blocks] = blocks
+ @scope[:constraints] = constraints
end
- def nesting_name_prefix
- "#{shallow_name_prefix}#{singular}_"
+ def controller(controller)
+ scope(controller.to_sym) { yield }
end
- def action_separator
- @action_separator ||= ActionController::Base.resource_action_separator
+ def namespace(path)
+ scope(path.to_s) { yield }
end
- def uncountable?
- @singular.to_s == @plural.to_s
+ def constraints(constraints = {})
+ scope(:constraints => constraints) { yield }
end
+ end
- def has_action?(action)
- !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
+ class Constraints
+ def initialize(app, constraints = [])
+ @app, @constraints = app, constraints
end
- protected
- def arrange_actions
- @collection_methods = arrange_actions_by_methods(options.delete(:collection))
- @member_methods = arrange_actions_by_methods(options.delete(:member))
- @new_methods = arrange_actions_by_methods(options.delete(:new))
- end
-
- def add_default_actions
- add_default_action(member_methods, :get, :edit)
- add_default_action(new_methods, :get, :new)
- end
-
- def set_allowed_actions
- only, except = @options.values_at(:only, :except)
- @allowed_actions ||= {}
-
- if only == :all || except == :none
- only = nil
- except = []
- elsif only == :none || except == :all
- only = []
- except = nil
- end
-
- if only
- @allowed_actions[:only] = Array(only).map {|a| a.to_sym }
- elsif except
- @allowed_actions[:except] = Array(except).map {|a| a.to_sym }
- end
- end
-
- def action_allowed?(action)
- only, except = @allowed_actions.values_at(:only, :except)
- (!only || only.include?(action)) && (!except || !except.include?(action))
- end
-
- def set_prefixes
- @path_prefix = options.delete(:path_prefix)
- @name_prefix = options.delete(:name_prefix)
- end
+ def call(env)
+ req = Rack::Request.new(env)
- def arrange_actions_by_methods(actions)
- (actions || {}).inject({}) do |flipped_hash, (key, value)|
- (flipped_hash[value] ||= []) << key
- flipped_hash
+ @constraints.each { |constraint|
+ if constraint.respond_to?(:matches?) && !constraint.matches?(req)
+ return Rack::Mount::Const::EXPECTATION_FAILED_RESPONSE
+ elsif constraint.respond_to?(:call) && !constraint.call(req)
+ return Rack::Mount::Const::EXPECTATION_FAILED_RESPONSE
end
- end
+ }
- def add_default_action(collection, method, action)
- (collection[method] ||= []).unshift(action)
- end
+ @app.call(env)
+ end
end
- class SingletonResource < Resource #:nodoc:
- def initialize(entity, options)
- @singular = @plural = entity
- options[:controller] ||= @singular.to_s.pluralize
- super
- end
+ def initialize(set)
+ @set = set
+ @scope = {}
- alias_method :shallow_path_prefix, :path_prefix
- alias_method :shallow_name_prefix, :name_prefix
- alias_method :member_path, :path
- alias_method :nesting_path_prefix, :path
+ extend Scoping
+ extend Resources
end
- # Creates named routes for implementing verb-oriented controllers
- # for a collection \resource.
- #
- # For example:
- #
- # map.resources :messages
- #
- # will map the following actions in the corresponding controller:
- #
- # class MessagesController < ActionController::Base
- # # GET messages_url
- # def index
- # # return all messages
- # end
- #
- # # GET new_message_url
- # def new
- # # return an HTML form for describing a new message
- # end
- #
- # # POST messages_url
- # def create
- # # create a new message
- # end
- #
- # # GET message_url(:id => 1)
- # def show
- # # find and return a specific message
- # end
- #
- # # GET edit_message_url(:id => 1)
- # def edit
- # # return an HTML form for editing a specific message
- # end
- #
- # # PUT message_url(:id => 1)
- # def update
- # # find and update a specific message
- # end
- #
- # # DELETE message_url(:id => 1)
- # def destroy
- # # delete a specific message
- # end
- # end
- #
- # Along with the routes themselves, +resources+ generates named routes for use in
- # controllers and views. map.resources :messages produces the following named routes and helpers:
- #
- # Named Route Helpers
- # ============ =====================================================
- # messages messages_url, hash_for_messages_url,
- # messages_path, hash_for_messages_path
- #
- # message message_url(id), hash_for_message_url(id),
- # message_path(id), hash_for_message_path(id)
- #
- # new_message new_message_url, hash_for_new_message_url,
- # new_message_path, hash_for_new_message_path
- #
- # edit_message edit_message_url(id), hash_for_edit_message_url(id),
- # edit_message_path(id), hash_for_edit_message_path(id)
- #
- # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
- #
- # redirect_to :controller => 'messages', :action => 'index'
- # # and
- # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
- #
- # now become:
- #
- # redirect_to messages_url
- # # and
- # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
- #
- # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
- # form tags. The form helpers make this a little easier. For an update form with a @message object:
- #
- # <%= form_tag message_path(@message), :method => :put %>
- #
- # or
- #
- # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
- #
- # or
- #
- # <% form_for @message do |f| %>
- #
- # which takes into account whether @message is a new record or not and generates the
- # path and method accordingly.
- #
- # The +resources+ method accepts the following options to customize the resulting routes:
- # * :collection - Add named routes for other actions that operate on the collection.
- # Takes a hash of #{action} => #{method}, where method is :get/:post/:put/:delete,
- # an array of any of the previous, or :any if the method does not matter.
- # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
- # * :member - Same as :collection, but for actions that operate on a specific member.
- # * :new - Same as :collection, but for actions that operate on the new \resource action.
- # * :controller - Specify the controller name for the routes.
- # * :singular - Specify the singular name used in the member routes.
- # * :requirements - Set custom routing parameter requirements; this is a hash of either
- # regular expressions (which must match for the route to match) or extra parameters. For example:
- #
- # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
- #
- # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
- # * :conditions - Specify custom routing recognition conditions. \Resources sets the :method value for the method-specific routes.
- # * :as - Specify a different \resource name to use in the URL path. For example:
- # # products_path == '/productos'
- # map.resources :products, :as => 'productos' do |product|
- # # product_reviews_path(product) == '/productos/1234/comentarios'
- # product.resources :product_reviews, :as => 'comentarios'
- # end
- #
- # * :has_one - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
- # * :has_many - Same has :has_one, but for plural \resources.
- #
- # You may directly specify the routing association with +has_one+ and +has_many+ like:
- #
- # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
- #
- # This is the same as:
- #
- # map.resources :notes do |notes|
- # notes.resource :author
- # notes.resources :comments
- # notes.resources :attachments
- # end
- #
- # * :path_names - Specify different path names for the actions. For example:
- # # new_products_path == '/productos/nuevo'
- # # bids_product_path(1) == '/productos/1/licitacoes'
- # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
- #
- # You can also set default action names from an environment, like this:
- # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
- #
- # * :path_prefix - Set a prefix to the routes with required route variables.
- #
- # Weblog comments usually belong to a post, so you might use +resources+ like:
- #
- # map.resources :articles
- # map.resources :comments, :path_prefix => '/articles/:article_id'
- #
- # You can nest +resources+ calls to set this automatically:
- #
- # map.resources :articles do |article|
- # article.resources :comments
- # end
- #
- # The comment \resources work the same, but must now include a value for :article_id.
- #
- # article_comments_url(@article)
- # article_comment_url(@article, @comment)
- #
- # article_comments_url(:article_id => @article)
- # article_comment_url(:article_id => @article, :id => @comment)
- #
- # If you don't want to load all objects from the database you might want to use the article_id directly:
- #
- # articles_comments_url(@comment.article_id, @comment)
- #
- # * :name_prefix - Define a prefix for all generated routes, usually ending in an underscore.
- # Use this if you have named routes that may clash.
- #
- # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
- # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
- #
- # You may also use :name_prefix to override the generic named routes in a nested \resource:
- #
- # map.resources :articles do |article|
- # article.resources :comments, :name_prefix => nil
- # end
- #
- # This will yield named \resources like so:
- #
- # comments_url(@article)
- # comment_url(@article, @comment)
- #
- # * :shallow - If true, paths for nested resources which reference a specific member
- # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
- #
- # The :shallow option is inherited by any nested resource(s).
- #
- # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
- #
- # map.resources :users, :shallow => true do |user|
- # user.resources :posts do |post|
- # post.resources :comments
- # end
- # end
- # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
- # # also adds the usual named route called "user_posts"
- # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
- # # also adds the named route called "post"
- # # --> GET /posts/2/comments (maps to the CommentsController#index action)
- # # also adds the named route called "post_comments"
- # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
- # # also adds the named route called "comment"
- #
- # You may also use :shallow in combination with the +has_one+ and +has_many+ shorthand notations like:
- #
- # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
- #
- # * :only and :except - Specify which of the seven default actions should be routed to.
- #
- # :only and :except may be set to :all, :none, an action name or a
- # list of action names. By default, routes are generated for all seven actions.
- #
- # For example:
- #
- # map.resources :posts, :only => [:index, :show] do |post|
- # post.resources :comments, :except => [:update, :destroy]
- # end
- # # --> GET /posts (maps to the PostsController#index action)
- # # --> POST /posts (fails)
- # # --> GET /posts/1 (maps to the PostsController#show action)
- # # --> DELETE /posts/1 (fails)
- # # --> POST /posts/1/comments (maps to the CommentsController#create action)
- # # --> PUT /posts/1/comments/1 (fails)
- #
- # If map.resources is called with multiple resources, they all get the same options applied.
- #
- # Examples:
- #
- # map.resources :messages, :path_prefix => "/thread/:thread_id"
- # # --> GET /thread/7/messages/1
- #
- # map.resources :messages, :collection => { :rss => :get }
- # # --> GET /messages/rss (maps to the #rss action)
- # # also adds a named route called "rss_messages"
- #
- # map.resources :messages, :member => { :mark => :post }
- # # --> POST /messages/1/mark (maps to the #mark action)
- # # also adds a named route called "mark_message"
- #
- # map.resources :messages, :new => { :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- #
- # map.resources :messages, :new => { :new => :any, :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- # # --> /messages/new can be invoked via any request method
- #
- # map.resources :messages, :controller => "categories",
- # :path_prefix => "/category/:category_id",
- # :name_prefix => "category_"
- # # --> GET /categories/7/messages/1
- # # has named route "category_message"
- #
- # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
- # HTTP POST on new_message_url will raise a RoutingError exception. The default route in
- # config/routes.rb overrides this and allows invalid HTTP methods for \resource routes.
- def resources(*entities, &block)
- options = entities.extract_options!
- entities.each { |entity| map_resource(entity, options.dup, &block) }
+ def get(*args, &block)
+ map_method(:get, *args, &block)
end
- # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
- # A singleton \resource is global to its current context. For unnested singleton \resources,
- # the \resource is global to the current user visiting the application, such as a user's
- # /account profile. For nested singleton \resources, the \resource is global to its parent
- # \resource, such as a projects \resource that has_one :project_manager.
- # The project_manager should be mapped as a singleton \resource under projects:
- #
- # map.resources :projects do |project|
- # project.resource :project_manager
- # end
- #
- # See +resources+ for general conventions. These are the main differences:
- # * A singular name is given to map.resource. The default controller name is still taken from the plural name.
- # * To specify a custom plural name, use the :plural option. There is no :singular option.
- # * No default index route is created for the singleton \resource controller.
- # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
- #
- # For example:
- #
- # map.resource :account
- #
- # maps these actions in the Accounts controller:
- #
- # class AccountsController < ActionController::Base
- # # GET new_account_url
- # def new
- # # return an HTML form for describing the new account
- # end
- #
- # # POST account_url
- # def create
- # # create an account
- # end
- #
- # # GET account_url
- # def show
- # # find and return the account
- # end
- #
- # # GET edit_account_url
- # def edit
- # # return an HTML form for editing the account
- # end
- #
- # # PUT account_url
- # def update
- # # find and update the account
- # end
- #
- # # DELETE account_url
- # def destroy
- # # delete the account
- # end
- # end
- #
- # Along with the routes themselves, +resource+ generates named routes for
- # use in controllers and views. map.resource :account produces
- # these named routes and helpers:
- #
- # Named Route Helpers
- # ============ =============================================
- # account account_url, hash_for_account_url,
- # account_path, hash_for_account_path
- #
- # new_account new_account_url, hash_for_new_account_url,
- # new_account_path, hash_for_new_account_path
- #
- # edit_account edit_account_url, hash_for_edit_account_url,
- # edit_account_path, hash_for_edit_account_path
- def resource(*entities, &block)
- options = entities.extract_options!
- entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
+ def post(*args, &block)
+ map_method(:post, *args, &block)
end
- private
- def map_resource(entities, options = {}, &block)
- resource = Resource.new(entities, options)
+ def put(*args, &block)
+ map_method(:put, *args, &block)
+ end
- with_options :controller => resource.controller do |map|
- map_associations(resource, options)
+ def delete(*args, &block)
+ map_method(:delete, *args, &block)
+ end
- if block_given?
- with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
- end
+ def match(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
- map_collection_actions(map, resource)
- map_default_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
- end
+ if args.length > 1
+ args.each { |path| match(path, options) }
+ return self
end
- def map_singleton_resource(entities, options = {}, &block)
- resource = SingletonResource.new(entities, options)
-
- with_options :controller => resource.controller do |map|
- map_associations(resource, options)
-
- if block_given?
- with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
- end
-
- map_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
- map_default_singleton_actions(map, resource)
- end
+ if args.first.is_a?(Symbol)
+ return match(args.first.to_s, options.merge(:to => args.first.to_sym))
end
- def map_associations(resource, options)
- map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
+ path = args.first
- path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
- name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
+ options = (@scope[:options] || {}).merge(options)
+ conditions, defaults = {}, {}
- Array(options[:has_one]).each do |association|
- resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
- end
- end
-
- def map_has_many_associations(resource, associations, options)
- case associations
- when Hash
- associations.each do |association,has_many|
- map_has_many_associations(resource, association, options.merge(:has_many => has_many))
- end
- when Array
- associations.each do |association|
- map_has_many_associations(resource, association, options)
- end
- when Symbol, String
- resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
- else
- end
- end
+ path = nil if path == ""
+ path = Rack::Mount::Utils.normalize_path(path) if path
+ path = "#{@scope[:path]}#{path}" if @scope[:path]
- def map_collection_actions(map, resource)
- resource.collection_methods.each do |method, actions|
- actions.each do |action|
- [method].flatten.each do |m|
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
- action_path ||= action
+ raise ArgumentError, "path is required" unless path
- map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
- end
- end
- end
+ constraints = options[:constraints] || {}
+ unless constraints.is_a?(Hash)
+ block, constraints = constraints, {}
end
+ blocks = ((@scope[:blocks] || []) + [block]).compact
+ constraints = (@scope[:constraints] || {}).merge(constraints)
+ options.each { |k, v| constraints[k] = v if v.is_a?(Regexp) }
- def map_default_collection_actions(map, resource)
- index_route_name = "#{resource.name_prefix}#{resource.plural}"
-
- if resource.uncountable?
- index_route_name << "_index"
- end
+ conditions[:path_info] = Rack::Mount::Strexp.compile(path, constraints, %w( / . ? ))
- map_resource_routes(map, resource, :index, resource.path, index_route_name)
- map_resource_routes(map, resource, :create, resource.path, index_route_name)
- end
+ segment_keys = Rack::Mount::RegexpWithNamedGroups.new(conditions[:path_info]).names
+ constraints.reject! { |k, v| segment_keys.include?(k.to_s) }
+ conditions.merge!(constraints)
- def map_default_singleton_actions(map, resource)
- map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
+ if via = options[:via]
+ via = Array(via).map { |m| m.to_s.upcase }
+ conditions[:request_method] = Regexp.union(*via)
end
- def map_new_actions(map, resource)
- resource.new_methods.each do |method, actions|
- actions.each do |action|
- route_path = resource.new_path
- route_name = "new_#{resource.name_prefix}#{resource.singular}"
+ defaults[:controller] = @scope[:controller].to_s if @scope[:controller]
- unless action == :new
- route_path = "#{route_path}#{resource.action_separator}#{action}"
- route_name = "#{action}_#{route_name}"
- end
-
- map_resource_routes(map, resource, action, route_path, route_name, method)
- end
- end
+ 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)
- def map_member_actions(map, resource)
- resource.member_methods.each do |method, actions|
- actions.each do |action|
- [method].flatten.each do |m|
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
- action_path ||= ActionController::Base.resources_path_names[action] || action
-
- map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
- end
- end
+ if 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
-
- route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
- map_resource_routes(map, resource, :show, resource.member_path, route_path)
- map_resource_routes(map, resource, :update, resource.member_path, route_path)
- map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
end
- def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
- if resource.has_action?(action)
- action_options = action_options_for(action, resource, method, resource_options)
- formatted_route_path = "#{route_path}.:format"
+ app = Constraints.new(app, blocks) if blocks.any?
+ @set.add_route(app, conditions, defaults, options[:as])
- if route_name && @set.named_routes[route_name.to_sym].nil?
- map.named_route(route_name, formatted_route_path, action_options)
- else
- map.connect(formatted_route_path, action_options)
- end
- end
- end
+ self
+ end
- def add_conditions_for(conditions, method)
- returning({:conditions => conditions.dup}) do |options|
- options[:conditions][:method] = method unless method == :any
- end
- 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
- def action_options_for(action, resource, method = nil, resource_options = {})
- default_options = { :action => action.to_s }
- require_id = !resource.kind_of?(SingletonResource)
- force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
-
- case default_options[:action]
- when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
- when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
- when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
- when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
- when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
- else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
- 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
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index a6e46b1c78..90d7c208a5 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -208,9 +208,9 @@ module ActionDispatch
clear!
end
- def draw
+ def draw(&block)
clear!
- yield Mapper.new(self)
+ Mapper.new(self).instance_exec(DeprecatedMapper.new(self), &block)
@set.add_route(NotFound)
install_helpers
@set.freeze
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 92373b5d26..04e9acf855 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -41,7 +41,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_should_arrange_actions
- resource = ActionDispatch::Routing::Mapper::Resource.new(:messages,
+ resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages,
:collection => { :rss => :get, :reorder => :post, :csv => :post },
:member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post },
:new => { :preview => :get, :draft => :get })
@@ -54,18 +54,18 @@ class ResourcesTest < ActionController::TestCase
end
def test_should_resource_controller_name_equal_resource_name_by_default
- resource = ActionDispatch::Routing::Mapper::Resource.new(:messages, {})
+ resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, {})
assert_equal 'messages', resource.controller
end
def test_should_resource_controller_name_equal_controller_option
- resource = ActionDispatch::Routing::Mapper::Resource.new(:messages, :controller => 'posts')
+ resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, :controller => 'posts')
assert_equal 'posts', resource.controller
end
def test_should_all_singleton_paths_be_the_same
[ :path, :nesting_path_prefix, :member_path ].each do |method|
- resource = ActionDispatch::Routing::Mapper::SingletonResource.new(:messages, :path_prefix => 'admin')
+ resource = ActionDispatch::Routing::DeprecatedMapper::SingletonResource.new(:messages, :path_prefix => 'admin')
assert_equal 'admin/messages', resource.send(method)
end
end
@@ -121,7 +121,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_override_paths_for_default_restful_actions
- resource = ActionDispatch::Routing::Mapper::Resource.new(:messages,
+ resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages,
:path_names => {:new => 'nuevo', :edit => 'editar'})
assert_equal resource.new_path, "#{resource.path}/nuevo"
end
@@ -281,7 +281,7 @@ class ResourcesTest < ActionController::TestCase
def test_with_member_action_and_requirement
expected_options = {:controller => 'messages', :action => 'mark', :id => '1.1.1'}
-
+
with_restful_routing(:messages, :requirements => {:id => /[0-9]\.[0-9]\.[0-9]/}, :member => { :mark => :get }) do
assert_recognizes(expected_options, :path => 'messages/1.1.1/mark', :method => :get)
end
@@ -701,8 +701,8 @@ class ResourcesTest < ActionController::TestCase
def test_should_not_allow_invalid_head_method_for_member_routes
with_routing do |set|
- set.draw do |map|
- assert_raise(ArgumentError) do
+ assert_raise(ArgumentError) do
+ set.draw do |map|
map.resources :messages, :member => {:something => :head}
end
end
@@ -711,8 +711,8 @@ class ResourcesTest < ActionController::TestCase
def test_should_not_allow_invalid_http_methods_for_member_routes
with_routing do |set|
- set.draw do |map|
- assert_raise(ArgumentError) do
+ assert_raise(ArgumentError) do
+ set.draw do |map|
map.resources :messages, :member => {:something => :invalid}
end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 308e2a85b1..d7e4646df5 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -2046,3 +2046,335 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
assert true
end
end
+
+class TestRoutingMapper < ActiveSupport::TestCase
+ include Rack::Test::Methods
+
+ SprocketsApp = lambda { |env|
+ [200, {"Content-Type" => "text/html"}, ["javascripts"]]
+ }
+
+ class IpRestrictor
+ def self.matches?(request)
+ request.ip =~ /192\.168\.1\.1\d\d/
+ end
+ end
+
+ class Dispatcher
+ def self.new(*args)
+ lambda { |env|
+ params = env['action_dispatch.request.path_parameters']
+ controller, action = params[:controller], params[:action]
+ [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]]
+ }
+ end
+ end
+ old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher
+ ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
+ ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, Dispatcher }
+
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw do
+ controller :sessions do
+ get 'login', :to => :new, :as => :login
+ post 'login', :to => :create
+
+ delete 'logout', :to => :destroy, :as => :logout
+ end
+
+ match 'account/login', :to => redirect("/login")
+
+ match 'openid/login', :via => [:get, :post], :to => "openid#login"
+
+ controller(:global) do
+ match 'global/:action'
+ match 'global/export', :to => :export, :as => :export_request
+ match 'global/hide_notice', :to => :hide_notice, :as => :hide_notice
+ match '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ }
+ end
+
+ constraints(:ip => /192\.168\.1\.\d\d\d/) do
+ get 'admin', :to => "queenbee#index"
+ end
+
+ constraints IpRestrictor do
+ get 'admin/accounts', :to => "queenbee#accounts"
+ end
+
+ resources :projects, :controller => :project do
+ resources :involvements, :attachments
+
+ resources :participants do
+ put :update_all, :on => :collection
+ end
+
+ resources :companies do
+ resources :people
+ resource :avatar
+ end
+
+ resources :images do
+ post :revise, :on => :member
+ end
+
+ resources :people do
+ namespace ":access_token" do
+ resource :avatar
+ end
+
+ member do
+ put :accessible_projects
+ post :resend, :generate_new_password
+ end
+ end
+
+ resources :posts do
+ get :archive, :toggle_view, :on => :collection
+ post :preview, :on => :member
+
+ resource :subscription
+
+ resources :comments do
+ post :preview, :on => :collection
+ end
+ end
+ end
+
+ match 'sprockets.js', :to => SprocketsApp
+
+ match 'people/:id/update', :to => 'people#update', :as => :update_person
+ match '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person
+
+ # misc
+ match 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
+
+ namespace :account do
+ resource :subscription, :credit, :credit_card
+ end
+
+ controller :articles do
+ scope 'articles' do
+ scope ':title', :title => /[a-z]+/, :as => :with_title do
+ match ':id', :to => :with_id
+ end
+ end
+ end
+
+ scope ':access_token', :constraints => { :access_token => /\w{5,5}/ } do
+ resources :rooms
+ end
+ end
+ ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
+ ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher }
+
+ def app
+ Routes
+ end
+
+ def test_logout
+ delete '/logout'
+ assert_equal 'sessions#destroy', last_response.body
+
+ # assert_equal '/logout', app.logout_path
+ end
+
+ def test_login
+ get '/login'
+ assert_equal 'sessions#new', last_response.body
+
+ post '/login'
+ assert_equal 'sessions#create', last_response.body
+
+ # assert_equal '/login', app.login_path
+ end
+
+ def test_login_redirect
+ get '/account/login'
+ assert_equal 301, last_response.status
+ assert_equal 'http://example.org/login', last_response.headers['Location']
+ assert_equal 'Moved Permanently', last_response.body
+ end
+
+ def test_openid
+ get '/openid/login'
+ assert_equal 'openid#login', last_response.body
+
+ post '/openid/login'
+ assert_equal 'openid#login', last_response.body
+ end
+
+ # def test_admin
+ # get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ # assert_equal 'queenbee#index', last_response.body
+ #
+ # assert_raise(ActionController::RoutingError) { get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
+ #
+ # get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ # assert_equal 'queenbee#accounts', last_response.body
+ #
+ # assert_raise(ActionController::RoutingError) { get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
+ # end
+
+ def test_global
+ get '/global/dashboard'
+ assert_equal 'global#dashboard', last_response.body
+
+ get '/global/export'
+ assert_equal 'global#export', last_response.body
+
+ get '/global/hide_notice'
+ assert_equal 'global#hide_notice', last_response.body
+
+ get '/export/123/foo.txt'
+ assert_equal 'global#export', last_response.body
+
+ # assert_equal '/global/export', app.export_request_path
+ # assert_equal '/global/hide_notice', app.hide_notice_path
+ # assert_equal '/export/123/foo.txt', app.export_download_path(:id => 123, :file => 'foo.txt')
+ end
+
+ def test_projects
+ get '/projects/1'
+ assert_equal 'projects#show', last_response.body
+ end
+
+ def test_projects_involvements
+ get '/projects/1/involvements'
+ assert_equal 'involvements#index', last_response.body
+
+ get '/projects/1/involvements/1'
+ assert_equal 'involvements#show', last_response.body
+ end
+
+ def test_projects_attachments
+ get '/projects/1/attachments'
+ assert_equal 'attachments#index', last_response.body
+ end
+
+ def test_projects_participants
+ get '/projects/1/participants'
+ assert_equal 'participants#index', last_response.body
+
+ put '/projects/1/participants/update_all'
+ assert_equal 'participants#update_all', last_response.body
+ end
+
+ def test_projects_companies
+ get '/projects/1/companies'
+ assert_equal 'companies#index', last_response.body
+
+ get '/projects/1/companies/1/people'
+ assert_equal 'people#index', last_response.body
+
+ get '/projects/1/companies/1/avatar'
+ assert_equal 'avatar#show', last_response.body
+ end
+
+ def test_project_images
+ get '/projects/1/images'
+ assert_equal 'images#index', last_response.body
+
+ post '/projects/1/images/1/revise'
+ assert_equal 'images#revise', last_response.body
+ end
+
+ def test_projects_people
+ get '/projects/1/people'
+ assert_equal 'people#index', last_response.body
+
+ get '/projects/1/people/1'
+ assert_equal 'people#show', last_response.body
+
+ get '/projects/1/people/1/7a2dec8/avatar'
+ assert_equal 'avatar#show', last_response.body
+
+ put '/projects/1/people/1/accessible_projects'
+ assert_equal 'people#accessible_projects', last_response.body
+
+ post '/projects/1/people/1/resend'
+ assert_equal 'people#resend', last_response.body
+
+ post '/projects/1/people/1/generate_new_password'
+ assert_equal 'people#generate_new_password', last_response.body
+ end
+
+ def test_projects_posts
+ get '/projects/1/posts'
+ assert_equal 'posts#index', last_response.body
+
+ get '/projects/1/posts/archive'
+ assert_equal 'posts#archive', last_response.body
+
+ get '/projects/1/posts/toggle_view'
+ assert_equal 'posts#toggle_view', last_response.body
+
+ post '/projects/1/posts/1/preview'
+ assert_equal 'posts#preview', last_response.body
+
+ get '/projects/1/posts/1/subscription'
+ assert_equal 'subscription#show', last_response.body
+
+ get '/projects/1/posts/1/comments'
+ assert_equal 'comments#index', last_response.body
+
+ post '/projects/1/posts/1/comments/preview'
+ assert_equal 'comments#preview', last_response.body
+ end
+
+ def test_sprockets
+ get '/sprockets.js'
+ assert_equal 'javascripts', last_response.body
+ end
+
+ def test_update_person_route
+ get '/people/1/update'
+ assert_equal 'people#update', last_response.body
+
+ # assert_equal '/people/1/update', app.update_person_path(:id => 1)
+ end
+
+ def test_update_project_person
+ get '/projects/1/people/2/update'
+ assert_equal 'people#update', last_response.body
+
+ # assert_equal '/projects/1/people/2/update', app.update_project_person_path(:project_id => 1, :id => 2)
+ end
+
+ def test_articles_perma
+ get '/articles/2009/08/18/rails-3'
+ assert_equal 'articles#show', last_response.body
+
+ # assert_equal '/articles/2009/8/18/rails-3', app.article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3')
+ end
+
+ def test_account_namespace
+ get '/account/subscription'
+ assert_equal 'subscription#show', last_response.body
+
+ get '/account/credit'
+ assert_equal 'credit#show', last_response.body
+
+ get '/account/credit_card'
+ assert_equal 'credit_card#show', last_response.body
+ end
+
+ def test_articles_with_id
+ get '/articles/rails/1'
+ assert_equal 'articles#with_id', last_response.body
+
+ assert_raise(ActionController::RoutingError) { get '/articles/123/1' }
+
+ # assert_equal '/articles/rails/1', app.with_title_path(:title => 'rails', :id => 1)
+ end
+
+ def test_access_token_rooms
+ get '/12345/rooms'
+ assert_equal 'rooms#index', last_response.body
+
+ get '/12345/rooms/1'
+ assert_equal 'rooms#show', last_response.body
+
+ get '/12345/rooms/1/edit'
+ assert_equal 'rooms#edit', last_response.body
+ end
+end
--
cgit v1.2.3
From 207d0483e57b8d017ddd0c225d30a3e4fc9edc0c Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 20 Oct 2009 15:28:34 -0500
Subject: Run bundler before AM and AMo tests
---
ci/ci_build.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ci/ci_build.rb b/ci/ci_build.rb
index 6574481c19..c49fb5f0c4 100755
--- a/ci/ci_build.rb
+++ b/ci/ci_build.rb
@@ -50,7 +50,7 @@ cd "#{root_dir}/activemodel" do
puts
puts "[CruiseControl] Building ActiveModel"
puts
- build_results[:activemodel] = system 'rake'
+ build_results[:activemodel] = system 'gem bundle && rake'
end
rm_f "#{root_dir}/activeresource/debug.log"
@@ -73,7 +73,7 @@ cd "#{root_dir}/actionmailer" do
puts
puts "[CruiseControl] Building ActionMailer"
puts
- build_results[:actionmailer] = system 'rake'
+ build_results[:actionmailer] = system 'gem bundle && rake'
end
cd "#{root_dir}/railties" do
--
cgit v1.2.3
From a5c82a9dfb6d63bf90a3378da0b71d6ea592d7e3 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 20 Oct 2009 16:03:55 -0500
Subject: Start rewriting some internal tests to use the new routing dsl
---
actionpack/lib/action_controller/metal.rb | 4 ++++
actionpack/test/controller/base_test.rb | 24 +++++++++++-----------
actionpack/test/controller/integration_test.rb | 2 +-
actionpack/test/controller/rescue_test.rb | 6 +++---
actionpack/test/controller/url_rewriter_test.rb | 8 +++-----
actionpack/test/controller/verification_test.rb | 4 ++--
.../dispatch/request/json_params_parsing_test.rb | 2 +-
.../dispatch/request/query_string_parsing_test.rb | 2 +-
.../request/url_encoded_params_parsing_test.rb | 2 +-
.../dispatch/request/xml_params_parsing_test.rb | 2 +-
.../test/dispatch/session/cookie_store_test.rb | 2 +-
.../test/dispatch/session/mem_cache_store_test.rb | 2 +-
actionpack/test/template/test_test.rb | 3 +--
13 files changed, 32 insertions(+), 31 deletions(-)
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index e9007d3631..6f89bf5d67 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -109,6 +109,10 @@ module ActionController
middleware_stack
end
+ def self.call(env)
+ action(env['action_dispatch.request.path_parameters'][:action]).call(env)
+ end
+
# Return a rack endpoint for the given action. Memoize the endpoint, so
# multiple calls into MyController.action will return the same object
# for the same action.
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index b97ceb4594..b57550a69a 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -10,12 +10,12 @@ module Submodule
def public_action
render :nothing => true
end
-
+
hide_action :hidden_action
def hidden_action
raise "Noooo!"
end
-
+
def another_hidden_action
end
hide_action :another_hidden_action
@@ -30,25 +30,25 @@ class NonEmptyController < ActionController::Base
def public_action
render :nothing => true
end
-
+
hide_action :hidden_action
def hidden_action
end
end
class MethodMissingController < ActionController::Base
-
+
hide_action :shouldnt_be_called
def shouldnt_be_called
raise "NO WAY!"
end
-
+
protected
-
+
def method_missing(selector)
render :text => selector.to_s
end
-
+
end
class DefaultUrlOptionsController < ActionController::Base
@@ -79,7 +79,7 @@ class ControllerInstanceTests < Test::Unit::TestCase
@empty = EmptyController.new
@contained = Submodule::ContainedEmptyController.new
@empty_controllers = [@empty, @contained, Submodule::SubclassedController.new]
-
+
@non_empty_controllers = [NonEmptyController.new,
Submodule::ContainedNonEmptyController.new]
end
@@ -135,24 +135,24 @@ class PerformActionTest < ActionController::TestCase
rescue_action_in_public!
end
-
+
def test_get_on_priv_should_show_selector
use_controller MethodMissingController
get :shouldnt_be_called
assert_response :success
assert_equal 'shouldnt_be_called', @response.body
end
-
+
def test_method_missing_is_not_an_action_name
use_controller MethodMissingController
assert ! @controller.__send__(:action_method?, 'method_missing')
-
+
get :method_missing
assert_response :success
assert_equal 'method_missing', @response.body
end
-
+
def test_get_on_hidden_should_fail
use_controller NonEmptyController
assert_raise(ActionController::UnknownAction) { get :hidden_action }
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index fe95fb5750..d6e2a5a974 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -390,7 +390,7 @@ class IntegrationProcessTest < ActionController::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do |map|
- map.connect "/:action", :controller => "integration_process_test/integration"
+ match ':action', :to => IntegrationController
end
yield
end
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 054a9f2aaf..2b1f532b8d 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -343,9 +343,9 @@ class RescueTest < ActionController::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do |map|
- map.connect 'foo', :controller => "rescue_test/test", :action => 'foo'
- map.connect 'invalid', :controller => "rescue_test/test", :action => 'invalid'
- map.connect 'b00m', :controller => "rescue_test/test", :action => 'b00m'
+ match 'foo', :to => TestController.action(:foo)
+ match 'invalid', :to => TestController.action(:invalid)
+ match 'b00m', :to => TestController.action(:b00m)
end
yield
end
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index d81ced96a8..3b14cbb2d8 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -224,9 +224,8 @@ class UrlWriterTests < ActionController::TestCase
def test_named_routes
with_routing do |set|
set.draw do |map|
- map.no_args '/this/is/verbose', :controller => 'home', :action => 'index'
- map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
- map.connect ':controller/:action/:id'
+ match 'this/is/verbose', :to => 'home#index', :as => :no_args
+ match 'home/sweet/home/:user', :to => 'home#index', :as => :home
end
# We need to create a new class in order to install the new named route.
@@ -264,7 +263,7 @@ class UrlWriterTests < ActionController::TestCase
def test_only_path
with_routing do |set|
set.draw do |map|
- map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
+ match 'home/sweet/home/:user', :to => 'home#index', :as => :home
map.connect ':controller/:action/:id'
end
@@ -334,7 +333,6 @@ class UrlWriterTests < ActionController::TestCase
set.draw do |map|
map.main '', :controller => 'posts', :format => nil
map.resources :posts
- map.connect ':controller/:action/:id'
end
# We need to create a new class in order to install the new named route.
diff --git a/actionpack/test/controller/verification_test.rb b/actionpack/test/controller/verification_test.rb
index 1a9eb65f29..63e8cf3e61 100644
--- a/actionpack/test/controller/verification_test.rb
+++ b/actionpack/test/controller/verification_test.rb
@@ -125,8 +125,8 @@ class VerificationTest < ActionController::TestCase
assert_not_deprecated do
with_routing do |set|
set.draw do |map|
- map.foo '/foo', :controller => 'test', :action => 'foo'
- map.connect ":controller/:action/:id"
+ match 'foo', :to => 'test#foo', :as => :foo
+ match 'verification_test/:action', :to => TestController
end
get :guarded_one_for_named_route_test, :two => "not one"
assert_redirected_to '/foo'
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index db6cf7b330..3c2408de5f 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -57,7 +57,7 @@ class JsonParamsParsingTest < ActionController::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do |map|
- map.connect ':action', :controller => "json_params_parsing_test/test"
+ match ':action', :to => TestController
end
yield
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index a31e326ddf..b764478d87 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -109,7 +109,7 @@ class QueryStringParsingTest < ActionController::IntegrationTest
def assert_parses(expected, actual)
with_routing do |set|
set.draw do |map|
- map.connect ':action', :controller => "query_string_parsing_test/test"
+ match ':action', :to => TestController
end
get "/parse", actual
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 7167cdafac..e98a49980e 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -130,7 +130,7 @@ class UrlEncodedParamsParsingTest < ActionController::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do |map|
- map.connect ':action', :controller => "url_encoded_params_parsing_test/test"
+ match ':action', :to => TestController
end
yield
end
diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
index 521002b519..0dc47ed9d5 100644
--- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
@@ -84,7 +84,7 @@ class XmlParamsParsingTest < ActionController::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do |map|
- map.connect ':action', :controller => "xml_params_parsing_test/test"
+ match ':action', :to => TestController
end
yield
end
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index ab5fabde65..edfc303d3d 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -219,7 +219,7 @@ class CookieStoreTest < ActionController::IntegrationTest
def with_test_route_set(options = {})
with_routing do |set|
set.draw do |map|
- map.connect "/:action", :controller => "cookie_store_test/test"
+ match ':action', :to => TestController
end
options = {:key => SessionKey, :secret => SessionSecret}.merge(options)
@app = ActionDispatch::Session::CookieStore.new(set, options)
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index c7435bd06b..afc9d91d50 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -112,7 +112,7 @@ class MemCacheStoreTest < ActionController::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do |map|
- map.connect "/:action", :controller => "mem_cache_store_test/test"
+ match ':action', :to => TestController
end
@app = ActionDispatch::Session::MemCacheStore.new(set, :key => '_session_id')
yield
diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb
index 05a14f3554..68e790cf46 100644
--- a/actionpack/test/template/test_test.rb
+++ b/actionpack/test/template/test_test.rb
@@ -48,8 +48,7 @@ class PeopleHelperTest < ActionView::TestCase
def with_test_route_set
with_routing do |set|
set.draw do |map|
- map.people 'people', :controller => 'people', :action => 'index'
- map.connect ':controller/:action/:id'
+ match 'people', :to => 'people#index', :as => :people
end
yield
end
--
cgit v1.2.3
From 4f6d6f7031a88b647814fc0154e6b69b636dc912 Mon Sep 17 00:00:00 2001
From: Yehuda Katz + Carl Lerche
Date: Tue, 20 Oct 2009 16:33:54 -0700
Subject: Have all the tests running off a single Gemfile
---
.gitignore | 6 +-----
Gemfile | 21 +++++++++++++++++++++
actionmailer/Gemfile | 10 ----------
actionmailer/test/abstract_unit.rb | 14 +++++++-------
actionpack/Gemfile | 18 ------------------
actionpack/test/abstract_unit.rb | 15 +++++++--------
activemodel/Gemfile | 12 ------------
activemodel/test/cases/helper.rb | 13 ++++++-------
activerecord/Gemfile | 8 --------
activerecord/test/cases/helper.rb | 13 ++++++-------
activeresource/test/abstract_unit.rb | 10 ++++++++--
.../lib/active_support/testing/isolation.rb | 2 --
activesupport/lib/active_support/vendor.rb | 5 ++++-
activesupport/test/abstract_unit.rb | 9 ++++++++-
railties/Gemfile | 13 -------------
railties/test/abstract_unit.rb | 15 +++++++--------
railties/test/application/initializer_test.rb | 20 --------------------
railties/test/isolation/abstract_unit.rb | 19 ++++++++-----------
18 files changed, 83 insertions(+), 140 deletions(-)
create mode 100644 Gemfile
delete mode 100644 actionmailer/Gemfile
delete mode 100644 actionpack/Gemfile
delete mode 100644 activemodel/Gemfile
delete mode 100644 activerecord/Gemfile
delete mode 100644 railties/Gemfile
diff --git a/.gitignore b/.gitignore
index ea0d8bbba7..3b922f29f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,10 +27,6 @@ railties/guides/output
*.rbc
*.swp
*.swo
-actionpack/bin
-activerecord/bin
+bin
vendor/gems/
-*/vendor/gems/
railties/tmp
-activerecord/vendor
-actionpack/vendor
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000000..ba55d96783
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,21 @@
+Gem.sources.each { |uri| source uri }
+
+gem "rails", "3.0.pre", :vendored_at => "railties"
+%w(
+ activesupport
+ activemodel
+ actionpack
+ actionmailer
+ activerecord
+ activeresource
+).each do |lib|
+ gem lib, '3.0.pre', :vendored_at => lib
+end
+gem "rack", "1.0.1"
+gem "rack-mount", :git => "git://github.com/josh/rack-mount.git"
+gem "rack-test", "~> 0.5.0"
+gem "erubis", "~> 2.6.0"
+gem "arel", :git => "git://github.com/rails/arel.git"
+gem "mocha"
+gem "sqlite3-ruby"
+gem "RedCloth"
\ No newline at end of file
diff --git a/actionmailer/Gemfile b/actionmailer/Gemfile
deleted file mode 100644
index 430b32e8ca..0000000000
--- a/actionmailer/Gemfile
+++ /dev/null
@@ -1,10 +0,0 @@
-Gem.sources.each { |uri| source uri }
-sibling = "#{File.dirname(__FILE__)}/.."
-
-gem "activesupport", "3.0.pre", :vendored_at => "#{sibling}/activesupport"
-gem "activemodel", "3.0.pre", :vendored_at => "#{sibling}/activemodel"
-gem "actionpack", "3.0.pre", :vendored_at => "#{sibling}/actionpack"
-gem "rack-mount", :git => "git://github.com/josh/rack-mount.git"
-gem "mocha"
-
-#disable_system_gems
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 7843623996..fcbaa9e186 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -1,15 +1,15 @@
-bundled = "#{File.dirname(__FILE__)}/../vendor/gems/environment"
-if File.exist?("#{bundled}.rb")
- require bundled
-else
- $:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
- $:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib"
+root = File.expand_path('../../..', __FILE__)
+begin
+ require "#{root}/vendor/gems/environment"
+rescue LoadError
+ $:.unshift("#{root}/activesupport/lib")
+ $:.unshift("#{root}/actionpack/lib")
+ $:.unshift("#{root}/actionmailer/lib")
end
require 'rubygems'
require 'test/unit'
-$:.unshift "#{File.dirname(__FILE__)}/../lib"
require 'action_mailer'
require 'action_mailer/test_case'
diff --git a/actionpack/Gemfile b/actionpack/Gemfile
deleted file mode 100644
index af95766608..0000000000
--- a/actionpack/Gemfile
+++ /dev/null
@@ -1,18 +0,0 @@
-rails_root = Pathname.new(File.dirname(__FILE__)).join("..")
-
-Gem.sources.each { |uri| source uri }
-
-gem "rack", "1.0.1", :git => "git://github.com/rails/rack.git", :branch => "rack-1.0"
-gem "rack-mount", :git => "git://github.com/josh/rack-mount.git"
-gem "rack-test", "~> 0.5.0"
-gem "activesupport", "3.0.pre", :vendored_at => rails_root.join("activesupport")
-gem "activemodel", "3.0.pre", :vendored_at => rails_root.join("activemodel")
-gem "arel", :git => "git://github.com/rails/arel.git"
-gem "activerecord", "3.0.pre", :vendored_at => rails_root.join("activerecord")
-gem "erubis", "~> 2.6.0"
-
-gem "mocha"
-gem "sqlite3-ruby"
-gem "RedCloth"
-
-disable_system_gems
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index f7a1564f90..05b15d38ee 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -1,13 +1,12 @@
-bundled = "#{File.dirname(__FILE__)}/../vendor/gems/environment"
-if File.exist?("#{bundled}.rb")
- require bundled
-else
- $:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
- $:.unshift "#{File.dirname(__FILE__)}/../../activemodel/lib"
+root = File.expand_path('../../..', __FILE__)
+begin
+ require "#{root}/vendor/gems/environment"
+rescue LoadError
+ $:.unshift "#{root}/activesupport/lib"
+ $:.unshift "#{root}/activemodel/lib"
+ $:.unshift "#{root}/lib"
end
-$:.unshift(File.dirname(__FILE__) + '/../lib')
-
$:.unshift(File.dirname(__FILE__) + '/lib')
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers')
diff --git a/activemodel/Gemfile b/activemodel/Gemfile
deleted file mode 100644
index a192fdaf82..0000000000
--- a/activemodel/Gemfile
+++ /dev/null
@@ -1,12 +0,0 @@
-rails_root = Pathname.new(File.dirname(__FILE__)).join("..")
-
-Gem.sources.each { |uri| source uri }
-
-gem "activesupport", "3.0.pre", :vendored_at => rails_root.join("activesupport")
-gem "arel", :git => "git://github.com/rails/arel.git"
-
-only :test do
- gem "sqlite3-ruby"
-end
-
-disable_system_gems
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index e54c85938b..49783c2735 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -1,13 +1,12 @@
-bundled = "#{File.dirname(__FILE__)}/../vendor/gems/environment"
-if File.exist?("#{bundled}.rb")
- require bundled
-else
- $:.unshift(File.dirname(__FILE__) + '/../../lib')
- $:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib')
+root = File.expand_path('../../../..', __FILE__)
+begin
+ require "#{root}/vendor/gems/environment"
+rescue LoadError
+ $:.unshift("#{root}/activesupport/lib")
+ $:.unshift("#{root}/activemodel/lib")
end
require 'config'
-
require 'active_model'
# Show backtraces for deprecated behavior for quicker cleanup.
diff --git a/activerecord/Gemfile b/activerecord/Gemfile
deleted file mode 100644
index 3201e65f02..0000000000
--- a/activerecord/Gemfile
+++ /dev/null
@@ -1,8 +0,0 @@
-Gem.sources.each { |uri| source uri }
-sibling = "#{File.dirname(__FILE__)}/.."
-
-gem "activesupport", "3.0.pre", :vendored_at => "#{sibling}/activesupport"
-gem "activemodel", "3.0.pre", :vendored_at => "#{sibling}/activemodel"
-gem "arel", :git => "git://github.com/rails/arel.git"
-
-gem "mocha"
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 63014c0297..871cfa6468 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -1,10 +1,9 @@
-$:.unshift(File.dirname(__FILE__) + '/../../lib')
-
-bundled = "#{File.dirname(__FILE__)}/../../vendor/gems/environment"
-if File.exist?("#{bundled}.rb")
- require bundled
-else
- $:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib')
+root = File.expand_path('../../../..', __FILE__)
+begin
+ require "#{root}/vendor/gems/environment"
+rescue LoadError
+ $:.unshift("#{root}/activesupport/lib")
+ $:.unshift("#{root}/activerecord/lib")
end
require 'config'
diff --git a/activeresource/test/abstract_unit.rb b/activeresource/test/abstract_unit.rb
index 6a33040243..ee12f785b5 100644
--- a/activeresource/test/abstract_unit.rb
+++ b/activeresource/test/abstract_unit.rb
@@ -1,9 +1,15 @@
+root = File.expand_path('../../..', __FILE__)
+begin
+ require "#{root}/vendor/gems/environment"
+rescue LoadError
+ $:.unshift("#{root}/activesupport/lib")
+ $:.unshift("#{root}/activeresource/lib")
+end
+
require 'rubygems'
require 'test/unit'
require 'active_support'
require 'active_support/test_case'
-
-$:.unshift "#{File.dirname(__FILE__)}/../lib"
require 'active_resource'
$:.unshift "#{File.dirname(__FILE__)}/../test"
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 8f7d1c29a9..c75b59c284 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/load_error"
-
module ActiveSupport
module Testing
class ProxyTestResult
diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb
index 9f464c8246..0f9b0c7554 100644
--- a/activesupport/lib/active_support/vendor.rb
+++ b/activesupport/lib/active_support/vendor.rb
@@ -1,3 +1,5 @@
+require 'pathname'
+
def ActiveSupport.requirable?(file)
$LOAD_PATH.any? { |p| Dir.glob("#{p}/#{file}.*").any? }
end
@@ -10,7 +12,8 @@ end
gem lib, "~> #{version}"
# Use the vendored lib if the gem's missing or we aren't using RubyGems.
rescue LoadError, NoMethodError
- $LOAD_PATH.unshift File.expand_path("#{File.dirname(__FILE__)}/vendor/#{lib}-#{version}/lib")
+ # There could be symlinks
+ $LOAD_PATH.unshift Pathname.new(__FILE__).dirname.join("vendor/#{lib}-#{version}/lib").realpath.to_s
end
end
end
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index af9656615c..f390c66838 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -1,4 +1,12 @@
ORIG_ARGV = ARGV.dup
+root = File.expand_path('../../..', __FILE__)
+begin
+ require "#{root}/vendor/gems/environment"
+rescue LoadError
+ $:.unshift("#{root}/activesupport/lib")
+ $:.unshift("#{root}/activerecord/lib")
+end
+
require 'test/unit'
@@ -11,7 +19,6 @@ rescue LoadError
end
ENV['NO_RELOAD'] = '1'
-$:.unshift "#{File.dirname(__FILE__)}/../lib"
require 'active_support'
require 'active_support/test_case'
diff --git a/railties/Gemfile b/railties/Gemfile
deleted file mode 100644
index c441d69ded..0000000000
--- a/railties/Gemfile
+++ /dev/null
@@ -1,13 +0,0 @@
-Gem.sources.each { |uri| source uri }
-sibling = "#{File.dirname(__FILE__)}/.."
-
-gem "mocha"
-
-gem "arel", :git => "git://github.com/rails/arel.git"
-gem "rack", "1.0.1", :git => "git://github.com/rails/rack.git", :branch => "rack-1.0"
-gem "rack-mount", :git => "git://github.com/josh/rack-mount.git"
-gem "rack-test", "~> 0.5.0"
-
-%w(activesupport activemodel actionpack actionmailer activerecord activeresource).each do |lib|
- gem lib, '3.0.pre', :vendored_at => "#{sibling}/#{lib}"
-end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 7977b45a57..47013d7797 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -1,16 +1,15 @@
ORIG_ARGV = ARGV.dup
-bundled = "#{File.dirname(__FILE__)}/../vendor/gems/environment"
-if File.exist?("#{bundled}.rb")
- require bundled
-else
- %w(activesupport activemodel activerecord actionpack actionmailer activeresource).each do |lib|
- $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../#{lib}/lib"
+root = File.expand_path('../../..', __FILE__)
+begin
+ require "#{root}/vendor/gems/environment"
+rescue LoadError
+ %w(activesupport activemodel activerecord actionpack actionmailer activeresource railties).each do |lib|
+ $:.unshift "#{root}/#{lib}/lib"
end
end
-$:.unshift File.dirname(__FILE__) + "/../lib"
-$:.unshift File.dirname(__FILE__) + "/../builtin/rails_info"
+$:.unshift "#{root}/railties/builtin/rails_info"
require 'stringio'
require 'test/unit'
diff --git a/railties/test/application/initializer_test.rb b/railties/test/application/initializer_test.rb
index f42954079b..719520bf68 100644
--- a/railties/test/application/initializer_test.rb
+++ b/railties/test/application/initializer_test.rb
@@ -82,26 +82,6 @@ module ApplicationTests
end
end
- test "action_pack is added to the load path if action_controller is required" do
- Rails::Initializer.run do |config|
- config.root = app_path
- config.frameworks = [:action_controller]
- end
- Rails.initialize!
-
- assert $:.include?("#{framework_path}/actionpack/lib")
- end
-
- test "action_pack is added to the load path if action_view is required" do
- Rails::Initializer.run do |config|
- config.root = app_path
- config.frameworks = [:action_view]
- end
- Rails.initialize!
-
- assert $:.include?("#{framework_path}/actionpack/lib")
- end
-
test "after_initialize block works correctly" do
Rails::Initializer.run do |config|
config.root = app_path
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 557292e7d3..11cabb2c0b 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -6,7 +6,6 @@
#
# It is also good to know what is the bare minimum to get
# Rails booted up.
-
require 'fileutils'
# TODO: Remove rubygems when possible
@@ -82,6 +81,7 @@ module TestHelpers
def build_app(options = {})
FileUtils.rm_rf(app_path)
FileUtils.cp_r(tmp_path('app_template'), app_path)
+ FileUtils.ln_s(RAILS_FRAMEWORK_ROOT, app_path('vendor/rails'))
# Delete the initializers unless requested
unless options[:initializers]
@@ -114,13 +114,10 @@ module TestHelpers
end
def boot_rails
- bundled = "#{File.dirname(__FILE__)}/../../vendor/gems/environment"
- if File.exist?("#{bundled}.rb")
- require bundled
- %w(railties railties/lib).each do |path|
- $LOAD_PATH.unshift File.expand_path("../../../../#{path}", __FILE__)
- end
- else
+ root = File.expand_path('../../../..', __FILE__)
+ begin
+ require "#{root}/vendor/gems/environment"
+ rescue LoadError
%w(
actionmailer/lib
actionpack/lib
@@ -131,8 +128,7 @@ module TestHelpers
railties/lib
railties
).reverse_each do |path|
- path = File.expand_path("../../../../#{path}", __FILE__)
- $:.unshift(path)
+ $:.unshift "#{root}/#{path}"
end
end
end
@@ -155,5 +151,6 @@ Module.new do
end
FileUtils.mkdir(tmp_path)
- `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails #{tmp_path('app_template')}`
+ root = File.expand_path('../../../..', __FILE__)
+ `#{Gem.ruby} -r #{root}/vendor/gems/environment #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails #{tmp_path('app_template')}`
end
--
cgit v1.2.3
From e714b499cc1f7ebc84f8d0e96607b79e60f2828d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Tue, 20 Oct 2009 22:20:01 -0200
Subject: Move validator, human_name and human_attribute_name to ActiveModel,
remove deprecated error messages and add i18n_scope and lookup_ancestors.
Signed-off-by: Carl Lerche
---
activemodel/lib/active_model.rb | 2 +
activemodel/lib/active_model/errors.rb | 19 +-
activemodel/lib/active_model/naming.rb | 9 +-
activemodel/lib/active_model/translation.rb | 59 ++
activemodel/lib/active_model/validations.rb | 1 +
activemodel/lib/active_model/validator.rb | 68 ++
activemodel/test/cases/naming_test.rb | 2 +-
activemodel/test/cases/translation_test.rb | 51 ++
.../i18n_generate_message_validation_test.rb | 1 -
.../test/cases/validations/i18n_validation_test.rb | 8 +
.../test/cases/validations/with_validation_test.rb | 8 +-
activerecord/lib/active_record.rb | 1 -
activerecord/lib/active_record/base.rb | 32 +-
activerecord/lib/active_record/validations.rb | 89 ---
activerecord/lib/active_record/validator.rb | 68 --
activerecord/test/cases/i18n_test.rb | 6 +-
activerecord/test/cases/reflection_test.rb | 4 +-
.../i18n_generate_message_validation_test.rb | 129 +---
.../test/cases/validations/i18n_validation_test.rb | 732 ---------------------
activerecord/test/cases/validations_test.rb | 31 -
20 files changed, 227 insertions(+), 1093 deletions(-)
create mode 100644 activemodel/lib/active_model/translation.rb
create mode 100644 activemodel/lib/active_model/validator.rb
create mode 100644 activemodel/test/cases/translation_test.rb
delete mode 100644 activerecord/lib/active_record/validator.rb
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 67f529262d..505e16c195 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -39,8 +39,10 @@ module ActiveModel
autoload :Serialization, 'active_model/serialization'
autoload :StateMachine, 'active_model/state_machine'
autoload :TestCase, 'active_model/test_case'
+ autoload :Translation, 'active_model/translation'
autoload :Validations, 'active_model/validations'
autoload :ValidationsRepairHelper, 'active_model/validations_repair_helper'
+ autoload :Validator, 'active_model/validator'
autoload :VERSION, 'active_model/version'
module Serializers
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 7a48960f89..e8bb62953d 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -93,7 +93,7 @@ module ActiveModel
# company = Company.create(:address => '123 First St.')
# company.errors.full_messages # =>
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
- def full_messages(options = {})
+ def full_messages
full_messages = []
each do |attribute, messages|
@@ -103,8 +103,10 @@ module ActiveModel
if attribute == :base
messages.each {|m| full_messages << m }
else
- attr_name = attribute.to_s.humanize
- prefix = attr_name + I18n.t('activemodel.errors.format.separator', :default => ' ')
+ attr_name = @base.class.human_attribute_name(attribute)
+ options = { :default => ' ', :scope => @base.class.i18n_scope }
+ prefix = attr_name + I18n.t(:"errors.format.separator", options)
+
messages.each do |m|
full_messages << "#{prefix}#{m}"
end
@@ -135,10 +137,7 @@ module ActiveModel
def generate_message(attribute, message = :invalid, options = {})
message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
- klass_ancestors = [@base.class]
- klass_ancestors += @base.class.ancestors.reject {|x| x.is_a?(Module)}
-
- defaults = klass_ancestors.map do |klass|
+ defaults = @base.class.lookup_ancestors.map do |klass|
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
:"models.#{klass.name.underscore}.#{message}" ]
end
@@ -150,10 +149,10 @@ module ActiveModel
value = @base.send(:read_attribute_for_validation, attribute)
options = { :default => defaults,
- :model => @base.class.name.humanize,
- :attribute => attribute.to_s.humanize,
+ :model => @base.class.model_name.human,
+ :attribute => @base.class.human_attribute_name(attribute),
:value => value,
- :scope => [:activemodel, :errors]
+ :scope => [@base.class.i18n_scope, :errors]
}.merge(options)
I18n.translate(key, options)
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index b8c2a367b4..675d62b9a6 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -5,12 +5,13 @@ module ActiveModel
attr_reader :singular, :plural, :element, :collection, :partial_path, :human
alias_method :cache_key, :collection
- def initialize(name)
- super
+ def initialize(klass, name)
+ super(name)
+ @klass = klass
@singular = ActiveSupport::Inflector.underscore(self).tr('/', '_').freeze
@plural = ActiveSupport::Inflector.pluralize(@singular).freeze
@element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze
- @human = @element.gsub(/_/, " ")
+ @human = ActiveSupport::Inflector.humanize(@element).freeze
@collection = ActiveSupport::Inflector.tableize(self).freeze
@partial_path = "#{@collection}/#{@element}".freeze
end
@@ -20,7 +21,7 @@ module ActiveModel
# Returns an ActiveModel::Name object for module. It can be
# used to retrieve all kinds of naming-related information.
def model_name
- @_model_name ||= ActiveModel::Name.new(name)
+ @_model_name ||= ActiveModel::Name.new(self, name)
end
end
end
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
new file mode 100644
index 0000000000..dc11198c66
--- /dev/null
+++ b/activemodel/lib/active_model/translation.rb
@@ -0,0 +1,59 @@
+module ActiveModel
+ module Translation
+ include ActiveModel::Naming
+
+ # Returns the i18n_scope for the class. Overwrite if you want custom lookup.
+ def i18n_scope
+ :activemodel
+ end
+
+ # When localizing a string, goes through the lookup returned by this method.
+ # Used in ActiveModel::Name#human, ActiveModel::Errors#full_messages and
+ # ActiveModel::Translation#human_attribute_name.
+ def lookup_ancestors
+ self.ancestors.select { |x| x.respond_to?(:model_name) }
+ end
+
+ # Transforms attributes names into a more human format, such as "First name" instead of "first_name".
+ #
+ # Example:
+ #
+ # Person.human_attribute_name("first_name") # => "First name"
+ #
+ # Specify +options+ with additional translating options.
+ def human_attribute_name(attribute, options = {})
+ defaults = lookup_ancestors.map do |klass|
+ :"#{klass.model_name.underscore}.#{attribute}"
+ end
+
+ defaults << options.delete(:default) if options[:default]
+ defaults << attribute.to_s.humanize
+
+ options.reverse_merge! :scope => [self.i18n_scope, :attributes], :count => 1, :default => defaults
+ I18n.translate(defaults.shift, options)
+ end
+
+ # Model.human_name is deprecated. Use Model.model_name.human instead.
+ def human_name(*args)
+ ActiveSupport::Deprecation.warn("human_name has been deprecated, please use model_name.human instead", caller[0,1])
+ model_name.human(*args)
+ end
+ end
+
+ class Name < String
+ # Transform the model name into a more humane format, using I18n. By default,
+ # it will underscore then humanize the class name (BlogPost.human_name #=> "Blog post").
+ # Specify +options+ with additional translating options.
+ def human(options={})
+ defaults = @klass.lookup_ancestors.map do |klass|
+ klass.model_name.underscore.to_sym
+ end
+
+ defaults << options.delete(:default) if options[:default]
+ defaults << @human
+
+ options.reverse_merge! :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults
+ I18n.translate(defaults.shift, options)
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 0f178a07c8..064ec98f3a 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -7,6 +7,7 @@ module ActiveModel
include ActiveSupport::Callbacks
included do
+ extend ActiveModel::Translation
define_callbacks :validate, :scope => :name
end
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
new file mode 100644
index 0000000000..09de72b757
--- /dev/null
+++ b/activemodel/lib/active_model/validator.rb
@@ -0,0 +1,68 @@
+module ActiveModel #:nodoc:
+
+ # A simple base class that can be used along with ActiveModel::Base.validates_with
+ #
+ # class Person < ActiveModel::Base
+ # validates_with MyValidator
+ # end
+ #
+ # class MyValidator < ActiveModel::Validator
+ # def validate
+ # if some_complex_logic
+ # record.errors[:base] = "This record is invalid"
+ # end
+ # end
+ #
+ # private
+ # def some_complex_logic
+ # # ...
+ # end
+ # end
+ #
+ # Any class that inherits from ActiveModel::Validator will have access to record,
+ # which is an instance of the record being validated, and must implement a method called validate.
+ #
+ # class Person < ActiveModel::Base
+ # validates_with MyValidator
+ # end
+ #
+ # class MyValidator < ActiveModel::Validator
+ # def validate
+ # record # => The person instance being validated
+ # options # => Any non-standard options passed to validates_with
+ # end
+ # end
+ #
+ # To cause a validation error, you must add to the record's errors directly
+ # from within the validators message
+ #
+ # class MyValidator < ActiveModel::Validator
+ # def validate
+ # record.errors[:base] << "This is some custom error message"
+ # record.errors[:first_name] << "This is some complex validation"
+ # # etc...
+ # end
+ # end
+ #
+ # To add behavior to the initialize method, use the following signature:
+ #
+ # class MyValidator < ActiveModel::Validator
+ # def initialize(record, options)
+ # super
+ # @my_custom_field = options[:field_name] || :first_name
+ # end
+ # end
+ #
+ class Validator
+ attr_reader :record, :options
+
+ def initialize(record, options)
+ @record = record
+ @options = options
+ end
+
+ def validate
+ raise "You must override this method"
+ end
+ end
+end
diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb
index 4d97af3d13..fe1ea36450 100644
--- a/activemodel/test/cases/naming_test.rb
+++ b/activemodel/test/cases/naming_test.rb
@@ -2,7 +2,7 @@ require 'cases/helper'
class NamingTest < ActiveModel::TestCase
def setup
- @model_name = ActiveModel::Name.new('Post::TrackBack')
+ @model_name = ActiveModel::Name.new(self, 'Post::TrackBack')
end
def test_singular
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
new file mode 100644
index 0000000000..d171784963
--- /dev/null
+++ b/activemodel/test/cases/translation_test.rb
@@ -0,0 +1,51 @@
+require 'cases/helper'
+
+class SuperUser
+ extend ActiveModel::Translation
+end
+
+class User < SuperUser
+end
+
+class ActiveModelI18nTests < ActiveModel::TestCase
+
+ def setup
+ I18n.backend = I18n::Backend::Simple.new
+ end
+
+ def test_translated_model_attributes
+ I18n.backend.store_translations 'en', :activemodel => {:attributes => {:super_user => {:name => 'super_user name attribute'} } }
+ assert_equal 'super_user name attribute', SuperUser.human_attribute_name('name')
+ end
+
+ def test_translated_model_attributes_with_symbols
+ I18n.backend.store_translations 'en', :activemodel => {:attributes => {:super_user => {:name => 'super_user name attribute'} } }
+ assert_equal 'super_user name attribute', SuperUser.human_attribute_name(:name)
+ end
+
+ def test_translated_model_attributes_with_ancestor
+ I18n.backend.store_translations 'en', :activemodel => {:attributes => {:user => {:name => 'user name attribute'} } }
+ assert_equal 'user name attribute', User.human_attribute_name('name')
+ end
+
+ def test_translated_model_attributes_with_ancestors_fallback
+ I18n.backend.store_translations 'en', :activemodel => {:attributes => {:super_user => {:name => 'super_user name attribute'} } }
+ assert_equal 'super_user name attribute', User.human_attribute_name('name')
+ end
+
+ def test_translated_model_names
+ I18n.backend.store_translations 'en', :activemodel => {:models => {:super_user => 'super_user model'} }
+ assert_equal 'super_user model', SuperUser.model_name.human
+ end
+
+ def test_translated_model_names_with_sti
+ I18n.backend.store_translations 'en', :activemodel => {:models => {:user => 'user model'} }
+ assert_equal 'user model', User.model_name.human
+ end
+
+ def test_translated_model_names_with_ancestors_fallback
+ I18n.backend.store_translations 'en', :activemodel => {:models => {:super_user => 'super_user model'} }
+ assert_equal 'super_user model', User.model_name.human
+ end
+end
+
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
index 443a81c6ac..54b2405c92 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -63,7 +63,6 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title')
end
- # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
# validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
def test_generate_message_invalid_with_default_message
assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, :default => nil, :value => 'title')
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index fc4f1926b0..68b1b27f75 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -56,6 +56,12 @@ class I18nValidationTest < ActiveModel::TestCase
@person.errors.add_on_blank :title, 'custom'
end
+ def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
+ @person.errors.add('name', 'empty')
+ I18n.expects(:translate).with(:"person.name", :default => ['Name'], :scope => [:activemodel, :attributes], :count => 1).returns('Name')
+ @person.errors.full_messages
+ end
+
# ActiveRecord::Validations
# validates_confirmation_of w/ mocha
def test_validates_confirmation_of_generates_message
@@ -494,6 +500,8 @@ class I18nValidationTest < ActiveModel::TestCase
assert_equal ['global message'], @person.errors[:title]
end
+ # test with validates_with
+
def test_validations_with_message_symbol_must_translate
I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:custom_error => "I am a custom error"}}}
Person.validates_presence_of :title, :message => :custom_error
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index c290b49a28..fae87a6188 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -13,24 +13,24 @@ class ValidatesWithTest < ActiveRecord::TestCase
ERROR_MESSAGE = "Validation error from validator"
OTHER_ERROR_MESSAGE = "Validation error from other validator"
- class ValidatorThatAddsErrors < ActiveRecord::Validator
+ class ValidatorThatAddsErrors < ActiveModel::Validator
def validate()
record.errors[:base] << ERROR_MESSAGE
end
end
- class OtherValidatorThatAddsErrors < ActiveRecord::Validator
+ class OtherValidatorThatAddsErrors < ActiveModel::Validator
def validate()
record.errors[:base] << OTHER_ERROR_MESSAGE
end
end
- class ValidatorThatDoesNotAddErrors < ActiveRecord::Validator
+ class ValidatorThatDoesNotAddErrors < ActiveModel::Validator
def validate()
end
end
- class ValidatorThatValidatesOptions < ActiveRecord::Validator
+ class ValidatorThatValidatesOptions < ActiveModel::Validator
def validate()
if options[:field] == :first_name
record.errors[:base] << ERROR_MESSAGE
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 88becfb482..8195e78826 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -71,7 +71,6 @@ module ActiveRecord
autoload :Timestamp, 'active_record/timestamp'
autoload :Transactions, 'active_record/transactions'
autoload :Types, 'active_record/types'
- autoload :Validator, 'active_record/validator'
autoload :Validations, 'active_record/validations'
module AttributeMethods
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 4274df54cc..4e6090458a 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1386,7 +1386,8 @@ module ActiveRecord #:nodoc:
subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
end
- def self_and_descendants_from_active_record#nodoc:
+ # Set the lookup ancestors for ActiveModel.
+ def lookup_ancestors #:nodoc:
klass = self
classes = [klass]
while klass != klass.base_class
@@ -1400,32 +1401,9 @@ module ActiveRecord #:nodoc:
[self]
end
- # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
- # Person.human_attribute_name("first_name") # => "First name"
- # This used to be deprecated in favor of humanize, but is now preferred, because it automatically uses the I18n
- # module now.
- # Specify +options+ with additional translating options.
- def human_attribute_name(attribute_key_name, options = {})
- defaults = self_and_descendants_from_active_record.map do |klass|
- :"#{klass.name.underscore}.#{attribute_key_name}"
- end
- defaults << options[:default] if options[:default]
- defaults.flatten!
- defaults << attribute_key_name.to_s.humanize
- options[:count] ||= 1
- I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
- end
-
- # Transform the modelname into a more humane format, using I18n.
- # By default, it will underscore then humanize the class name (BlogPost.human_name #=> "Blog post").
- # Default scope of the translation is activerecord.models
- # Specify +options+ with additional translating options.
- def human_name(options = {})
- defaults = self_and_descendants_from_active_record.map do |klass|
- :"#{klass.name.underscore}"
- end
- defaults << self.name.underscore.humanize
- I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
+ # Set the i18n scope to overwrite ActiveModel.
+ def i18n_scope #:nodoc:
+ :activerecord
end
# True if this isn't a concrete subclass needing a STI type condition.
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index e61b253192..0365cb592f 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -17,90 +17,6 @@ module ActiveRecord
end
end
- class Errors < ActiveModel::Errors
- class << self
- def default_error_messages
- message = "Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages')."
- ActiveSupport::Deprecation.warn(message)
-
- I18n.translate 'activerecord.errors.messages'
- end
- end
-
- # Returns all the full error messages in an array.
- #
- # class Company < ActiveRecord::Base
- # validates_presence_of :name, :address, :email
- # validates_length_of :name, :in => 5..30
- # end
- #
- # company = Company.create(:address => '123 First St.')
- # company.errors.full_messages # =>
- # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
- def full_messages(options = {})
- full_messages = []
-
- each do |attribute, messages|
- messages = Array.wrap(messages)
- next if messages.empty?
-
- if attribute == :base
- messages.each {|m| full_messages << m }
- else
- attr_name = @base.class.human_attribute_name(attribute.to_s)
- prefix = attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ')
- messages.each do |m|
- full_messages << "#{prefix}#{m}"
- end
- end
- end
-
- full_messages
- end
-
- # Translates an error message in it's default scope (activerecord.errrors.messages).
- # Error messages are first looked up in models.MODEL.attributes.ATTRIBUTE.MESSAGE, if it's not there,
- # it's looked up in models.MODEL.MESSAGE and if that is not there it returns the translation of the
- # default message (e.g. activerecord.errors.messages.MESSAGE). The translated model name,
- # translated attribute name and the value are available for interpolation.
- #
- # When using inheritance in your models, it will check all the inherited models too, but only if the model itself
- # hasn't been found. Say you have class Admin < User; end and you wanted the translation for the :blank
- # error +message+ for the title +attribute+, it looks for these translations:
- #
- #
- # - activerecord.errors.models.admin.attributes.title.blank
- # - activerecord.errors.models.admin.blank
- # - activerecord.errors.models.user.attributes.title.blank
- # - activerecord.errors.models.user.blank
- # - activerecord.errors.messages.blank
- # - any default you provided through the +options+ hash (in the activerecord.errors scope)
- #
- def generate_message(attribute, message = :invalid, options = {})
- message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
-
- defaults = @base.class.self_and_descendants_from_active_record.map do |klass|
- [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
- :"models.#{klass.name.underscore}.#{message}" ]
- end
-
- defaults << options.delete(:default)
- defaults = defaults.compact.flatten << :"messages.#{message}"
-
- key = defaults.shift
- value = @base.respond_to?(attribute) ? @base.send(attribute) : nil
-
- options = { :default => defaults,
- :model => @base.class.human_name,
- :attribute => @base.class.human_attribute_name(attribute.to_s),
- :value => value,
- :scope => [:activerecord, :errors]
- }.merge(options)
-
- I18n.translate(key, options)
- end
- end
-
module Validations
extend ActiveSupport::Concern
@@ -165,11 +81,6 @@ module ActiveRecord
errors.empty?
end
-
- # Returns the Errors object that holds all information about attribute error messages.
- def errors
- @errors ||= Errors.new(self)
- end
end
end
end
diff --git a/activerecord/lib/active_record/validator.rb b/activerecord/lib/active_record/validator.rb
deleted file mode 100644
index 83a33f4dcd..0000000000
--- a/activerecord/lib/active_record/validator.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-module ActiveRecord #:nodoc:
-
- # A simple base class that can be used along with ActiveRecord::Base.validates_with
- #
- # class Person < ActiveRecord::Base
- # validates_with MyValidator
- # end
- #
- # class MyValidator < ActiveRecord::Validator
- # def validate
- # if some_complex_logic
- # record.errors[:base] = "This record is invalid"
- # end
- # end
- #
- # private
- # def some_complex_logic
- # # ...
- # end
- # end
- #
- # Any class that inherits from ActiveRecord::Validator will have access to record,
- # which is an instance of the record being validated, and must implement a method called validate.
- #
- # class Person < ActiveRecord::Base
- # validates_with MyValidator
- # end
- #
- # class MyValidator < ActiveRecord::Validator
- # def validate
- # record # => The person instance being validated
- # options # => Any non-standard options passed to validates_with
- # end
- # end
- #
- # To cause a validation error, you must add to the record's errors directly
- # from within the validators message
- #
- # class MyValidator < ActiveRecord::Validator
- # def validate
- # record.errors[:base] << "This is some custom error message"
- # record.errors[:first_name] << "This is some complex validation"
- # # etc...
- # end
- # end
- #
- # To add behavior to the initialize method, use the following signature:
- #
- # class MyValidator < ActiveRecord::Validator
- # def initialize(record, options)
- # super
- # @my_custom_field = options[:field_name] || :first_name
- # end
- # end
- #
- class Validator
- attr_reader :record, :options
-
- def initialize(record, options)
- @record = record
- @options = options
- end
-
- def validate
- raise "You must override this method"
- end
- end
-end
diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb
index d59c53cec8..ae4dcfb81e 100644
--- a/activerecord/test/cases/i18n_test.rb
+++ b/activerecord/test/cases/i18n_test.rb
@@ -30,17 +30,17 @@ class ActiveRecordI18nTests < Test::Unit::TestCase
def test_translated_model_names
I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} }
- assert_equal 'topic model', Topic.human_name
+ assert_equal 'topic model', Topic.model_name.human
end
def test_translated_model_names_with_sti
I18n.backend.store_translations 'en', :activerecord => {:models => {:reply => 'reply model'} }
- assert_equal 'reply model', Reply.human_name
+ assert_equal 'reply model', Reply.model_name.human
end
def test_translated_model_names_with_sti_fallback
I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} }
- assert_equal 'topic model', Reply.human_name
+ assert_equal 'topic model', Reply.model_name.human
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index f3ed8ccd8d..99e248743a 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -15,8 +15,8 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_human_name
- assert_equal "Price estimate", PriceEstimate.human_name
- assert_equal "Subscriber", Subscriber.human_name
+ assert_equal "Price estimate", PriceEstimate.model_name.human
+ assert_equal "Subscriber", Subscriber.model_name.human
end
def test_column_null_not_null
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
index 3794a0ebb9..3f96d7973b 100644
--- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -17,26 +17,7 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
}
end
- # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
- def test_generate_message_inclusion_with_default_message
- assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title')
- end
-
- def test_generate_message_inclusion_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value)
- def test_generate_message_exclusion_with_default_message
- assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title')
- end
-
- def test_generate_message_exclusion_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title')
- end
-
# validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
- # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
def test_generate_message_invalid_with_default_message
assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title')
end
@@ -45,107 +26,6 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title')
end
- # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message])
- def test_generate_message_confirmation_with_default_message
- assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil)
- end
-
- def test_generate_message_confirmation_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message')
- end
-
- # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message])
- def test_generate_message_accepted_with_default_message
- assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil)
- end
-
- def test_generate_message_accepted_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message')
- end
-
- # add_on_empty: generate_message(attr, :empty, :default => custom_message)
- def test_generate_message_empty_with_default_message
- assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil)
- end
-
- def test_generate_message_empty_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message')
- end
-
- # add_on_blank: generate_message(attr, :blank, :default => custom_message)
- def test_generate_message_blank_with_default_message
- assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil)
- end
-
- def test_generate_message_blank_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message')
- end
-
- # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end)
- def test_generate_message_too_long_with_default_message
- assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10)
- end
-
- def test_generate_message_too_long_with_custom_message
- assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10)
- end
-
- # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
- def test_generate_message_too_short_with_default_message
- assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10)
- end
-
- def test_generate_message_too_short_with_custom_message
- assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10)
- end
-
- # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value)
- def test_generate_message_wrong_length_with_default_message
- assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10)
- end
-
- def test_generate_message_wrong_length_with_custom_message
- assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10)
- end
-
- # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
- def test_generate_message_not_a_number_with_default_message
- assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title')
- end
-
- def test_generate_message_not_a_number_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message])
- def test_generate_message_greater_than_with_default_message
- assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_greater_than_or_equal_to_with_default_message
- assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_equal_to_with_default_message
- assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_less_than_with_default_message
- assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_less_than_or_equal_to_with_default_message
- assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_odd_with_default_message
- assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_even_with_default_message
- assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10)
- end
-
# validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message])
def test_generate_message_taken_with_default_message
assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title')
@@ -155,4 +35,13 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title')
end
+ # ActiveRecord#RecordInvalid exception
+
+ test "RecordInvalid exception can be localized" do
+ topic = Topic.new
+ topic.errors.add(:title, :invalid)
+ topic.errors.add(:title, :blank)
+ assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message
+ end
+
end
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index 252138c0d6..532de67d99 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -30,20 +30,6 @@ class I18nValidationTest < ActiveRecord::TestCase
end
end
- def test_percent_s_interpolation_syntax_in_error_messages_was_deprecated
- assert_not_deprecated do
- default = "%s interpolation syntax was deprecated"
- assert_equal default, I18n.t(:does_not_exist, :default => default, :value => 'this')
- end
- end
-
- def test_percent_d_interpolation_syntax_in_error_messages_was_deprecated
- assert_not_deprecated do
- default = "%d interpolation syntaxes are deprecated"
- assert_equal default, I18n.t(:does_not_exist, :default => default, :count => 2)
- end
- end
-
# ActiveRecord::Errors
def test_errors_generate_message_translates_custom_model_attribute_key
I18n.expects(:translate).with(
@@ -162,722 +148,4 @@ class I18nValidationTest < ActiveRecord::TestCase
assert_equal ['global message'], replied_topic.errors[:replies]
end
- def test_errors_add_on_empty_generates_message
- @topic.errors.expects(:generate_message).with(:title, :empty, {:default => nil})
- @topic.errors.add_on_empty :title
- end
-
- def test_errors_add_on_empty_generates_message_with_custom_default_message
- @topic.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'})
- @topic.errors.add_on_empty :title, 'custom'
- end
-
- def test_errors_add_on_blank_generates_message
- @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
- @topic.errors.add_on_blank :title
- end
-
- def test_errors_add_on_blank_generates_message_with_custom_default_message
- @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
- @topic.errors.add_on_blank :title, 'custom'
- end
-
- def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
- @topic.errors.add('title', 'empty')
- I18n.expects(:translate).with(:"topic.title", :default => ['Title'], :scope => [:activerecord, :attributes], :count => 1).returns('Title')
- @topic.errors.full_messages :locale => 'en'
- end
-
- # ActiveRecord::Validations
- # validates_confirmation_of w/ mocha
- def test_validates_confirmation_of_generates_message
- Topic.validates_confirmation_of :title
- @topic.title_confirmation = 'foo'
- @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil})
- @topic.valid?
- end
-
- def test_validates_confirmation_of_generates_message_with_custom_default_message
- Topic.validates_confirmation_of :title, :message => 'custom'
- @topic.title_confirmation = 'foo'
- @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'})
- @topic.valid?
- end
-
- # validates_acceptance_of w/ mocha
-
- def test_validates_acceptance_of_generates_message
- Topic.validates_acceptance_of :title, :allow_nil => false
- @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => nil})
- @topic.valid?
- end
-
- def test_validates_acceptance_of_generates_message_with_custom_default_message
- Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false
- @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'})
- @topic.valid?
- end
-
- # validates_presence_of w/ mocha
-
- def test_validates_presence_of_generates_message
- Topic.validates_presence_of :title
- @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
- @topic.valid?
- end
-
- def test_validates_presence_of_generates_message_with_custom_default_message
- Topic.validates_presence_of :title, :message => 'custom'
- @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
- @topic.valid?
- end
-
- def test_validates_length_of_within_generates_message_with_title_too_short
- Topic.validates_length_of :title, :within => 3..5
- @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
- @topic.valid?
- end
-
- def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message
- Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom'
- @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
- @topic.valid?
- end
-
- def test_validates_length_of_within_generates_message_with_title_too_long
- Topic.validates_length_of :title, :within => 3..5
- @topic.title = 'this title is too long'
- @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil})
- @topic.valid?
- end
-
- def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message
- Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom'
- @topic.title = 'this title is too long'
- @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'})
- @topic.valid?
- end
-
- # validates_length_of :within w/ mocha
-
- def test_validates_length_of_within_generates_message_with_title_too_short
- Topic.validates_length_of :title, :within => 3..5
- @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
- @topic.valid?
- end
-
- def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message
- Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom'
- @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
- @topic.valid?
- end
-
- def test_validates_length_of_within_generates_message_with_title_too_long
- Topic.validates_length_of :title, :within => 3..5
- @topic.title = 'this title is too long'
- @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil})
- @topic.valid?
- end
-
- def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message
- Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom'
- @topic.title = 'this title is too long'
- @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'})
- @topic.valid?
- end
-
- # validates_length_of :is w/ mocha
-
- def test_validates_length_of_is_generates_message
- Topic.validates_length_of :title, :is => 5
- @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil})
- @topic.valid?
- end
-
- def test_validates_length_of_is_generates_message_with_custom_default_message
- Topic.validates_length_of :title, :is => 5, :message => 'custom'
- @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'})
- @topic.valid?
- end
-
- # validates_format_of w/ mocha
-
- def test_validates_format_of_generates_message
- Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
- @topic.title = '72x'
- @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil})
- @topic.valid?
- end
-
- def test_validates_format_of_generates_message_with_custom_default_message
- Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom'
- @topic.title = '72x'
- @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'})
- @topic.valid?
- end
-
- # validates_inclusion_of w/ mocha
-
- def test_validates_inclusion_of_generates_message
- Topic.validates_inclusion_of :title, :in => %w(a b c)
- @topic.title = 'z'
- @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil})
- @topic.valid?
- end
-
- def test_validates_inclusion_of_generates_message_with_custom_default_message
- Topic.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom'
- @topic.title = 'z'
- @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'})
- @topic.valid?
- end
-
- # validates_exclusion_of w/ mocha
-
- def test_validates_exclusion_of_generates_message
- Topic.validates_exclusion_of :title, :in => %w(a b c)
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil})
- @topic.valid?
- end
-
- def test_validates_exclusion_of_generates_message_with_custom_default_message
- Topic.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom'
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'})
- @topic.valid?
- end
-
- # validates_numericality_of without :only_integer w/ mocha
-
- def test_validates_numericality_of_generates_message
- Topic.validates_numericality_of :title
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
- @topic.valid?
- end
-
- def test_validates_numericality_of_generates_message_with_custom_default_message
- Topic.validates_numericality_of :title, :message => 'custom'
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
- @topic.valid?
- end
-
- # validates_numericality_of with :only_integer w/ mocha
-
- def test_validates_numericality_of_only_integer_generates_message
- Topic.validates_numericality_of :title, :only_integer => true
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
- @topic.valid?
- end
-
- def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message
- Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom'
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
- @topic.valid?
- end
-
- # validates_numericality_of :odd w/ mocha
-
- def test_validates_numericality_of_odd_generates_message
- Topic.validates_numericality_of :title, :only_integer => true, :odd => true
- @topic.title = 0
- @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil})
- @topic.valid?
- end
-
- def test_validates_numericality_of_odd_generates_message_with_custom_default_message
- Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom'
- @topic.title = 0
- @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'})
- @topic.valid?
- end
-
- # validates_numericality_of :less_than w/ mocha
-
- def test_validates_numericality_of_less_than_generates_message
- Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
- @topic.title = 1
- @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil})
- @topic.valid?
- end
-
- def test_validates_numericality_of_odd_generates_message_with_custom_default_message
- Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom'
- @topic.title = 1
- @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'})
- @topic.valid?
- end
-
- # validates_confirmation_of w/o mocha
-
- def test_validates_confirmation_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:confirmation => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}}
-
- Topic.validates_confirmation_of :title
- @topic.title_confirmation = 'foo'
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_confirmation_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}}
-
- Topic.validates_confirmation_of :title
- @topic.title_confirmation = 'foo'
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- # validates_acceptance_of w/o mocha
-
- def test_validates_acceptance_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:accepted => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}}
-
- Topic.validates_acceptance_of :title, :allow_nil => false
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_acceptance_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}}
-
- Topic.validates_acceptance_of :title, :allow_nil => false
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- # validates_presence_of w/o mocha
-
- def test_validates_presence_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:blank => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}}
-
- Topic.validates_presence_of :title
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_presence_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}}
-
- Topic.validates_presence_of :title
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- # validates_length_of :within w/o mocha
-
- def test_validates_length_of_within_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:too_short => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}}
-
- Topic.validates_length_of :title, :within => 3..5
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_length_of_within_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}}
-
- Topic.validates_length_of :title, :within => 3..5
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- # validates_length_of :is w/o mocha
-
- def test_validates_length_of_is_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
-
- Topic.validates_length_of :title, :is => 5
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_length_of_is_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
-
- Topic.validates_length_of :title, :is => 5
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- def test_validates_length_of_is_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
-
- Topic.validates_length_of :title, :is => 5
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_length_of_is_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
-
- Topic.validates_length_of :title, :is => 5
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
-
- # validates_format_of w/o mocha
-
- def test_validates_format_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:invalid => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
-
- Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_format_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
-
- Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- # validates_inclusion_of w/o mocha
-
- def test_validates_inclusion_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:inclusion => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}}
-
- Topic.validates_inclusion_of :title, :in => %w(a b c)
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_inclusion_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}}
-
- Topic.validates_inclusion_of :title, :in => %w(a b c)
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- # validates_exclusion_of w/o mocha
-
- def test_validates_exclusion_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:exclusion => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}}
-
- Topic.validates_exclusion_of :title, :in => %w(a b c)
- @topic.title = 'a'
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_exclusion_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}}
-
- Topic.validates_exclusion_of :title, :in => %w(a b c)
- @topic.title = 'a'
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- # validates_numericality_of without :only_integer w/o mocha
-
- def test_validates_numericality_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
-
- Topic.validates_numericality_of :title
- @topic.title = 'a'
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_numericality_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
-
- Topic.validates_numericality_of :title, :only_integer => true
- @topic.title = 'a'
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- # validates_numericality_of with :only_integer w/o mocha
-
- def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
-
- Topic.validates_numericality_of :title, :only_integer => true
- @topic.title = 'a'
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_numericality_of_only_integer_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
-
- Topic.validates_numericality_of :title, :only_integer => true
- @topic.title = 'a'
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- # validates_numericality_of :odd w/o mocha
-
- def test_validates_numericality_of_odd_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:odd => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}}
-
- Topic.validates_numericality_of :title, :only_integer => true, :odd => true
- @topic.title = 0
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_numericality_of_odd_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}}
-
- Topic.validates_numericality_of :title, :only_integer => true, :odd => true
- @topic.title = 0
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- # validates_numericality_of :less_than w/o mocha
-
- def test_validates_numericality_of_less_than_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:less_than => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}}
-
- Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
- @topic.title = 1
- @topic.valid?
- assert_equal ['custom message'], @topic.errors[:title]
- end
-
- def test_validates_numericality_of_less_than_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}}
-
- Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
- @topic.title = 1
- @topic.valid?
- assert_equal ['global message'], @topic.errors[:title]
- end
-
- def test_validations_with_message_symbol_must_translate
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:custom_error => "I am a custom error"}}}
- Topic.validates_presence_of :title, :message => :custom_error
- @topic.title = nil
- @topic.valid?
- assert_equal ["I am a custom error"], @topic.errors[:title]
- end
-
- def test_validates_with_message_symbol_must_translate_per_attribute
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}}
- Topic.validates_presence_of :title, :message => :custom_error
- @topic.title = nil
- @topic.valid?
- assert_equal ["I am a custom error"], @topic.errors[:title]
- end
-
- def test_validates_with_message_symbol_must_translate_per_model
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:custom_error => "I am a custom error"}}}}
- Topic.validates_presence_of :title, :message => :custom_error
- @topic.title = nil
- @topic.valid?
- assert_equal ["I am a custom error"], @topic.errors[:title]
- end
-
- def test_validates_with_message_string
- Topic.validates_presence_of :title, :message => "I am a custom error"
- @topic.title = nil
- @topic.valid?
- assert_equal ["I am a custom error"], @topic.errors[:title]
- end
-end
-
-class ActiveRecordValidationsGenerateMessageI18nTests < ActiveRecord::TestCase
-
- def setup
- @topic = Topic.new
- I18n.backend.store_translations :'en', {
- :activerecord => {
- :errors => {
- :messages => {
- :inclusion => "is not included in the list",
- :exclusion => "is reserved",
- :invalid => "is invalid",
- :confirmation => "doesn't match confirmation",
- :accepted => "must be accepted",
- :empty => "can't be empty",
- :blank => "can't be blank",
- :too_long => "is too long (maximum is {{count}} characters)",
- :too_short => "is too short (minimum is {{count}} characters)",
- :wrong_length => "is the wrong length (should be {{count}} characters)",
- :taken => "has already been taken",
- :not_a_number => "is not a number",
- :greater_than => "must be greater than {{count}}",
- :greater_than_or_equal_to => "must be greater than or equal to {{count}}",
- :equal_to => "must be equal to {{count}}",
- :less_than => "must be less than {{count}}",
- :less_than_or_equal_to => "must be less than or equal to {{count}}",
- :odd => "must be odd",
- :even => "must be even"
- }
- }
- }
- }
- end
-
- # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
- def test_generate_message_inclusion_with_default_message
- assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title')
- end
-
- def test_generate_message_inclusion_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value)
- def test_generate_message_exclusion_with_default_message
- assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title')
- end
-
- def test_generate_message_exclusion_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
- # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
- def test_generate_message_invalid_with_default_message
- assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title')
- end
-
- def test_generate_message_invalid_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message])
- def test_generate_message_confirmation_with_default_message
- assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil)
- end
-
- def test_generate_message_confirmation_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message')
- end
-
- # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message])
- def test_generate_message_accepted_with_default_message
- assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil)
- end
-
- def test_generate_message_accepted_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message')
- end
-
- # add_on_empty: generate_message(attr, :empty, :default => custom_message)
- def test_generate_message_empty_with_default_message
- assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil)
- end
-
- def test_generate_message_empty_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message')
- end
-
- # add_on_blank: generate_message(attr, :blank, :default => custom_message)
- def test_generate_message_blank_with_default_message
- assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil)
- end
-
- def test_generate_message_blank_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message')
- end
-
- # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end)
- def test_generate_message_too_long_with_default_message
- assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10)
- end
-
- def test_generate_message_too_long_with_custom_message
- assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10)
- end
-
- # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
- def test_generate_message_too_short_with_default_message
- assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10)
- end
-
- def test_generate_message_too_short_with_custom_message
- assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10)
- end
-
- # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value)
- def test_generate_message_wrong_length_with_default_message
- assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10)
- end
-
- def test_generate_message_wrong_length_with_custom_message
- assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10)
- end
-
- # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message])
- def test_generate_message_taken_with_default_message
- assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title')
- end
-
- def test_generate_message_taken_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
- def test_generate_message_not_a_number_with_default_message
- assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title')
- end
-
- def test_generate_message_not_a_number_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message])
- def test_generate_message_greater_than_with_default_message
- assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_greater_than_or_equal_to_with_default_message
- assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_equal_to_with_default_message
- assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_less_than_with_default_message
- assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_less_than_or_equal_to_with_default_message
- assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_odd_with_default_message
- assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_even_with_default_message
- assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10)
- end
- # ActiveRecord#RecordInvalid exception
-
- test "RecordInvalid exception can be localized" do
- topic = Topic.new
- topic.errors.add(:title, :invalid)
- topic.errors.add(:title, :blank)
- assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message
- end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 5cdb623eef..130231c622 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -148,40 +148,9 @@ class ValidationsTest < ActiveRecord::TestCase
assert_equal "100,000", d.salary_before_type_cast
end
- def test_validates_length_with_globally_modified_error_message
- defaults = ActiveSupport::Deprecation.silence { ActiveRecord::Errors.default_error_messages }
- original_message = defaults[:too_short]
- defaults[:too_short] = 'tu est trops petit hombre {{count}}'
-
- Topic.validates_length_of :title, :minimum => 10
- t = Topic.create(:title => 'too short')
- assert !t.valid?
-
- assert_equal ['tu est trops petit hombre 10'], t.errors[:title]
-
- ensure
- defaults[:too_short] = original_message
- end
-
def test_validates_acceptance_of_as_database_column
Topic.validates_acceptance_of(:author_name)
topic = Topic.create("author_name" => "Dan Brown")
assert_equal "Dan Brown", topic["author_name"]
end
-
- def test_deprecated_validation_instance_methods
- tom = DeprecatedPerson.new
-
- assert_deprecated do
- assert tom.invalid?
- assert_equal ["always invalid", "invalid on create"], tom.errors[:name]
- end
-
- tom.save(false)
-
- assert_deprecated do
- assert tom.invalid?
- assert_equal ["always invalid", "invalid on update"], tom.errors[:name]
- end
- end
end
--
cgit v1.2.3
From 9fbb2c571b65e0501bf3570a3d49e553a9ae39c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Wed, 21 Oct 2009 11:18:36 -0500
Subject: Fix error_messages_for when instance variable names are given.
Signed-off-by: Joshua Peek
---
actionpack/lib/action_view/helpers/active_model_helper.rb | 12 ++++++------
activemodel/lib/active_model/translation.rb | 3 +++
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 3c398fe4da..c70f29f098 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -191,13 +191,13 @@ module ActionView
options = params.extract_options!.symbolize_keys
objects = Array.wrap(options.delete(:object) || params).map do |object|
- unless object.respond_to?(:to_model)
- object = instance_variable_get("@#{object}")
- object = convert_to_model(object)
- else
- object = object.to_model
- options[:object_name] ||= object.class.model_name.human
+ object = instance_variable_get("@#{object}") unless object.respond_to?(:to_model)
+ object = convert_to_model(object)
+
+ if object.class.respond_to?(:model_name)
+ options[:object_name] ||= object.class.model_name.human.downcase
end
+
object
end
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index dc11198c66..2ad8ca9dea 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -45,6 +45,9 @@ module ActiveModel
# it will underscore then humanize the class name (BlogPost.human_name #=> "Blog post").
# Specify +options+ with additional translating options.
def human(options={})
+ return @human unless @klass.respond_to?(:lookup_ancestors) &&
+ @klass.respond_to?(:i18n_scope)
+
defaults = @klass.lookup_ancestors.map do |klass|
klass.model_name.underscore.to_sym
end
--
cgit v1.2.3
From 3e35d30c0cab711fcc9feddd23f2f682f5d0a050 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Wed, 21 Oct 2009 11:32:31 -0500
Subject: Always add actionpack/lib to load path for isolated tests
---
actionpack/test/abstract_unit.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 05b15d38ee..86c8a95a43 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -4,9 +4,9 @@ begin
rescue LoadError
$:.unshift "#{root}/activesupport/lib"
$:.unshift "#{root}/activemodel/lib"
- $:.unshift "#{root}/lib"
end
+$:.unshift(File.dirname(__FILE__) + '/../lib')
$:.unshift(File.dirname(__FILE__) + '/lib')
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers')
--
cgit v1.2.3
From 8a0f4564432bef9dde815dd6b768d088cfad16ed Mon Sep 17 00:00:00 2001
From: Carl Lerche
Date: Wed, 21 Oct 2009 15:45:11 -0700
Subject: Refactored railties' isolation tests to be able to run script/*
scripts.
---
railties/test/isolation/abstract_unit.rb | 17 +++++++++++++----
railties/test/plugins/vendored_test.rb | 19 +++++++++++++++++++
2 files changed, 32 insertions(+), 4 deletions(-)
create mode 100644 railties/test/plugins/vendored_test.rb
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 11cabb2c0b..462a4d8dea 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -81,7 +81,6 @@ module TestHelpers
def build_app(options = {})
FileUtils.rm_rf(app_path)
FileUtils.cp_r(tmp_path('app_template'), app_path)
- FileUtils.ln_s(RAILS_FRAMEWORK_ROOT, app_path('vendor/rails'))
# Delete the initializers unless requested
unless options[:initializers]
@@ -93,6 +92,12 @@ module TestHelpers
add_to_config 'config.action_controller.session = { :key => "_myapp_session", :secret => "bac838a849c1d5c4de2e6a50af826079" }'
end
+ def script(script)
+ Dir.chdir(app_path) do
+ `#{Gem.ruby} #{app_path}/script/#{script}`
+ end
+ end
+
def add_to_config(str)
environment = File.read("#{app_path}/config/application.rb")
if environment =~ /(\n\s*end\s*)\Z/
@@ -149,8 +154,12 @@ Module.new do
if File.exist?(tmp_path)
FileUtils.rm_rf(tmp_path)
end
-
FileUtils.mkdir(tmp_path)
- root = File.expand_path('../../../..', __FILE__)
- `#{Gem.ruby} -r #{root}/vendor/gems/environment #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails #{tmp_path('app_template')}`
+
+ environment = File.expand_path('../../../../vendor/gems/environment', __FILE__)
+
+ `#{Gem.ruby} -r #{environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails #{tmp_path('app_template')}`
+ File.open("#{tmp_path}/app_template/config/boot.rb", 'w') do |f|
+ f.puts "require '#{environment}' ; require 'rails'"
+ end
end
diff --git a/railties/test/plugins/vendored_test.rb b/railties/test/plugins/vendored_test.rb
new file mode 100644
index 0000000000..71de542ff7
--- /dev/null
+++ b/railties/test/plugins/vendored_test.rb
@@ -0,0 +1,19 @@
+require "isolation/abstract_unit"
+
+module ApplicationTests
+ class PluginTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ end
+
+ test "generates the plugin" do
+ script "generate plugin my_plugin"
+ File.open("#{app_path}/vendor/plugins/my_plugin/init.rb", 'w') do |f|
+ f.puts "OMG = 'hello'"
+ end
+ require "#{app_path}/config/environment"
+ end
+ end
+end
\ No newline at end of file
--
cgit v1.2.3
From a0049a6b429c6de46537cc31bf7b3ca48b4c1b2c Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Thu, 22 Oct 2009 20:13:46 -0700
Subject: Use rails/rack-mount
---
Gemfile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Gemfile b/Gemfile
index ba55d96783..e99e7c7591 100644
--- a/Gemfile
+++ b/Gemfile
@@ -12,10 +12,10 @@ gem "rails", "3.0.pre", :vendored_at => "railties"
gem lib, '3.0.pre', :vendored_at => lib
end
gem "rack", "1.0.1"
-gem "rack-mount", :git => "git://github.com/josh/rack-mount.git"
+gem "rack-mount", :git => "git://github.com/rails/rack-mount.git"
gem "rack-test", "~> 0.5.0"
gem "erubis", "~> 2.6.0"
gem "arel", :git => "git://github.com/rails/arel.git"
gem "mocha"
gem "sqlite3-ruby"
-gem "RedCloth"
\ No newline at end of file
+gem "RedCloth"
--
cgit v1.2.3
From a840c8afbf4e30b7bd9979e8cd70192c65be7a43 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 24 Oct 2009 18:08:54 -0500
Subject: Restore `rake routes` [#3402 state:resolved]
---
actionpack/lib/action_dispatch/routing.rb | 1 +
.../action_dispatch/routing/deprecated_mapper.rb | 3 +-
actionpack/lib/action_dispatch/routing/mapper.rb | 8 ++--
actionpack/lib/action_dispatch/routing/route.rb | 44 ++++++++++++++++++++++
.../lib/action_dispatch/routing/route_set.rb | 11 ++----
railties/lib/rails/tasks/routes.rake | 9 ++---
6 files changed, 57 insertions(+), 19 deletions(-)
create mode 100644 actionpack/lib/action_dispatch/routing/route.rb
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 3803929847..b9c377db2c 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -262,6 +262,7 @@ module ActionDispatch
module Routing
autoload :DeprecatedMapper, 'action_dispatch/routing/deprecated_mapper'
autoload :Mapper, 'action_dispatch/routing/mapper'
+ autoload :Route, 'action_dispatch/routing/route'
autoload :RouteSet, 'action_dispatch/routing/route_set'
SEPARATORS = %w( / . ? )
diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
index f2a1f10fa7..0564ba9797 100644
--- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
@@ -132,7 +132,6 @@ module ActionDispatch
path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
glob = $1.to_sym if path =~ /\/\*(\w+)$/
path = ::Rack::Mount::Utils.normalize_path(path)
- path = ::Rack::Mount::Strexp.compile(path, requirements, %w( / . ? ))
if glob && !defaults[glob].blank?
raise ActionController::RoutingError, "paths cannot have non-empty default values"
@@ -145,7 +144,7 @@ module ActionDispatch
conditions[:request_method] = method if method
conditions[:path_info] = path if path
- @set.add_route(app, conditions, defaults, name)
+ @set.add_route(app, conditions, requirements, defaults, name)
end
def optionalize_trailing_dynamic_segments(path, requirements, defaults) #:nodoc:
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index ab4193266a..d6d822842b 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -252,9 +252,11 @@ module ActionDispatch
constraints = (@scope[:constraints] || {}).merge(constraints)
options.each { |k, v| constraints[k] = v if v.is_a?(Regexp) }
- conditions[:path_info] = Rack::Mount::Strexp.compile(path, constraints, %w( / . ? ))
+ conditions[:path_info] = path
+ requirements = constraints.dup
- segment_keys = Rack::Mount::RegexpWithNamedGroups.new(conditions[:path_info]).names
+ path_regexp = Rack::Mount::Strexp.compile(path, constraints, SEPARATORS)
+ segment_keys = Rack::Mount::RegexpWithNamedGroups.new(path_regexp).names
constraints.reject! { |k, v| segment_keys.include?(k.to_s) }
conditions.merge!(constraints)
@@ -286,7 +288,7 @@ module ActionDispatch
end
app = Constraints.new(app, blocks) if blocks.any?
- @set.add_route(app, conditions, defaults, options[:as])
+ @set.add_route(app, conditions, requirements, defaults, options[:as])
self
end
diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb
new file mode 100644
index 0000000000..8990e207c2
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing/route.rb
@@ -0,0 +1,44 @@
+module ActionDispatch
+ module Routing
+ class Route #:nodoc:
+ attr_reader :app, :conditions, :defaults, :name
+ attr_reader :path, :requirements
+
+ def initialize(app, conditions = {}, requirements = {}, defaults = {}, name = nil)
+ @app = app
+ @defaults = defaults
+ @name = name
+
+ @requirements = requirements.merge(defaults)
+ @requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp)
+ @requirements.delete_if { |k, v|
+ v == Regexp.compile("[^#{SEPARATORS.join}]+")
+ }
+
+ if path = conditions[:path_info]
+ @path = path
+ conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS)
+ end
+
+ @conditions = conditions.inject({}) { |h, (k, v)|
+ h[k] = Rack::Mount::RegexpWithNamedGroups.new(v)
+ h
+ }
+ end
+
+ def verb
+ if verb = conditions[:verb]
+ verb.to_s.upcase
+ end
+ end
+
+ def segment_keys
+ @segment_keys ||= conditions[:path_info].names.compact.map { |key| key.to_sym }
+ end
+
+ def to_ary
+ [@app, @conditions, @defaults, @name]
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 90d7c208a5..93617e826d 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -47,11 +47,6 @@ module ActionDispatch
end
end
- module RouteExtensions
- def segment_keys
- conditions[:path_info].names.compact.map { |key| key.to_sym }
- end
- end
# A NamedRouteCollection instance is a collection of named routes, and also
# maintains an anonymous module that can be used to install helpers for the
@@ -290,9 +285,9 @@ module ActionDispatch
routes_changed_at
end
- def add_route(app, conditions = {}, defaults = {}, name = nil)
- route = @set.add_route(app, conditions, defaults, name)
- route.extend(RouteExtensions)
+ def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil)
+ route = Route.new(app, conditions, requirements, defaults, name)
+ @set.add_route(*route)
named_routes[name] = route if name
routes << route
route
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index abbf3258c1..2395d73b2f 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -3,16 +3,13 @@ task :routes => :environment do
all_routes = ENV['CONTROLLER'] ? ActionController::Routing::Routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : ActionController::Routing::Routes.routes
routes = all_routes.collect do |route|
name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s
- verb = route.conditions[:method].to_s.upcase
- segs = route.segments.inject("") { |str,s| str << s.to_s }
- segs.chop! if segs.length > 1
reqs = route.requirements.empty? ? "" : route.requirements.inspect
- {:name => name, :verb => verb, :segs => segs, :reqs => reqs}
+ {:name => name, :verb => route.verb.to_s, :path => route.path, :reqs => reqs}
end
name_width = routes.collect {|r| r[:name]}.collect {|n| n.length}.max
verb_width = routes.collect {|r| r[:verb]}.collect {|v| v.length}.max
- segs_width = routes.collect {|r| r[:segs]}.collect {|s| s.length}.max
+ path_width = routes.collect {|r| r[:path]}.collect {|s| s.length}.max
routes.each do |r|
- puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:segs].ljust(segs_width)} #{r[:reqs]}"
+ puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
end
end
--
cgit v1.2.3
From 6083a87d63af9ca1b66fedcb92cd4a395965173b Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Mon, 26 Oct 2009 11:16:17 -0500
Subject: Splat calls to_a not to_ary [#3423 state:resolved]
---
actionpack/lib/action_dispatch/routing/route.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb
index 8990e207c2..e3aaa29e76 100644
--- a/actionpack/lib/action_dispatch/routing/route.rb
+++ b/actionpack/lib/action_dispatch/routing/route.rb
@@ -36,7 +36,7 @@ module ActionDispatch
@segment_keys ||= conditions[:path_info].names.compact.map { |key| key.to_sym }
end
- def to_ary
+ def to_a
[@app, @conditions, @defaults, @name]
end
end
--
cgit v1.2.3
From 55ae53baadf37daf2c966bf5d9d67c1f954cb681 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Mon, 26 Oct 2009 11:23:39 -0500
Subject: Fix `rake routes` method name output [#3422 state:resolved]
---
actionpack/lib/action_dispatch/routing/route.rb | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb
index e3aaa29e76..f1431e7a37 100644
--- a/actionpack/lib/action_dispatch/routing/route.rb
+++ b/actionpack/lib/action_dispatch/routing/route.rb
@@ -27,8 +27,13 @@ module ActionDispatch
end
def verb
- if verb = conditions[:verb]
- verb.to_s.upcase
+ if method = conditions[:request_method]
+ case method
+ when Regexp
+ method.source.upcase
+ else
+ method.to_s.upcase
+ end
end
end
--
cgit v1.2.3
From 60cc86136bffc96bc62798538e08e0d28138d41c Mon Sep 17 00:00:00 2001
From: Yehuda Katz
Date: Mon, 26 Oct 2009 15:47:08 -0700
Subject: Base setup for config object in AC.
---
actionpack/lib/action_controller.rb | 1 +
actionpack/lib/action_controller/base.rb | 1 +
.../lib/action_controller/metal/configuration.rb | 28 ++++++++++++++++++++++
.../action_controller/metal/session_management.rb | 15 +++---------
4 files changed, 33 insertions(+), 12 deletions(-)
create mode 100644 actionpack/lib/action_controller/metal/configuration.rb
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 9db1a71202..c5de4361bb 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -2,6 +2,7 @@ module ActionController
autoload :Base, "action_controller/base"
autoload :Benchmarking, "action_controller/metal/benchmarking"
autoload :ConditionalGet, "action_controller/metal/conditional_get"
+ autoload :Configuration, "action_controller/metal/configuration"
autoload :Helpers, "action_controller/metal/helpers"
autoload :HideActions, "action_controller/metal/hide_actions"
autoload :Layouts, "action_controller/metal/layouts"
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 5338a70104..4c026fe5f7 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -15,6 +15,7 @@ module ActionController
include ActionController::ConditionalGet
include ActionController::RackConvenience
include ActionController::Benchmarking
+ include ActionController::Configuration
# Legacy modules
include SessionManagement
diff --git a/actionpack/lib/action_controller/metal/configuration.rb b/actionpack/lib/action_controller/metal/configuration.rb
new file mode 100644
index 0000000000..5c829853b7
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/configuration.rb
@@ -0,0 +1,28 @@
+module ActionController
+ module Configuration
+ extend ActiveSupport::Concern
+
+ def config
+ @config ||= self.class.config
+ end
+
+ def config=(config)
+ @config = config
+ end
+
+ module ClassMethods
+ def default_config
+ @default_config ||= {}
+ end
+
+ def config
+ self.config ||= default_config
+ end
+
+ def config=(config)
+ @config = ActiveSupport::OrderedHash.new
+ @config.merge!(config)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb
index 654aa08cd3..9f4c4b8b39 100644
--- a/actionpack/lib/action_controller/metal/session_management.rb
+++ b/actionpack/lib/action_controller/metal/session_management.rb
@@ -1,10 +1,8 @@
module ActionController #:nodoc:
module SessionManagement #:nodoc:
- def self.included(base)
- base.class_eval do
- extend ClassMethods
- end
- end
+ include ActiveSupport::Concern
+
+ include ActionController::Configuration
module ClassMethods
# Set the session store to be used for keeping the session data between requests.
@@ -35,13 +33,6 @@ module ActionController #:nodoc:
session_options.merge!(options)
end
- # Returns the hash used to configure the session. Example use:
- #
- # ActionController::Base.session_options[:secure] = true # session only available over HTTPS
- def session_options
- @session_options ||= {}
- end
-
def session(*args)
ActiveSupport::Deprecation.warn(
"Disabling sessions for a single controller has been deprecated. " +
--
cgit v1.2.3
From d7499f8ee8faa80d12dccae5baf5ab2acc79e77d Mon Sep 17 00:00:00 2001
From: Yehuda Katz
Date: Mon, 26 Oct 2009 17:13:43 -0700
Subject: Extract #head into its own module and simplify it
---
actionpack/lib/action_controller.rb | 1 +
.../lib/action_controller/metal/conditional_get.rb | 39 ++--------------------
actionpack/lib/action_controller/metal/head.rb | 27 +++++++++++++++
3 files changed, 31 insertions(+), 36 deletions(-)
create mode 100644 actionpack/lib/action_controller/metal/head.rb
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index c5de4361bb..03a40e4fce 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -3,6 +3,7 @@ module ActionController
autoload :Benchmarking, "action_controller/metal/benchmarking"
autoload :ConditionalGet, "action_controller/metal/conditional_get"
autoload :Configuration, "action_controller/metal/configuration"
+ autoload :Head, "action_controller/metal/head"
autoload :Helpers, "action_controller/metal/helpers"
autoload :HideActions, "action_controller/metal/hide_actions"
autoload :Layouts, "action_controller/metal/layouts"
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 8575d30335..52f5a9727e 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -3,6 +3,7 @@ module ActionController
extend ActiveSupport::Concern
include RackConvenience
+ include Head
# Sets the etag, last_modified, or both on the response and renders a
# "304 Not Modified" response if the request is already fresh.
@@ -27,43 +28,9 @@ module ActionController
response.etag = options[:etag] if options[:etag]
response.last_modified = options[:last_modified] if options[:last_modified]
+ response.cache_control[:public] = true if options[:public]
- if options[:public]
- response.cache_control[:public] = true
- end
-
- if request.fresh?(response)
- head :not_modified
- end
- end
-
- # Return a response that has no content (merely headers). The options
- # argument is interpreted to be a hash of header names and values.
- # This allows you to easily return a response that consists only of
- # significant headers:
- #
- # head :created, :location => person_path(@person)
- #
- # It can also be used to return exceptional conditions:
- #
- # return head(:method_not_allowed) unless request.post?
- # return head(:bad_request) unless valid_request?
- # render
- def head(*args)
- if args.length > 2
- raise ArgumentError, "too many arguments to head"
- elsif args.empty?
- raise ArgumentError, "too few arguments to head"
- end
- options = args.extract_options!
- status = args.shift || options.delete(:status) || :ok
- location = options.delete(:location)
-
- options.each do |key, value|
- headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s
- end
-
- render :nothing => true, :status => status, :location => location
+ head :not_modified if request.fresh?(response)
end
# Sets the etag and/or last_modified on the response and checks it against
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
new file mode 100644
index 0000000000..68fa0a0402
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -0,0 +1,27 @@
+module ActionController
+ module Head
+ # Return a response that has no content (merely headers). The options
+ # argument is interpreted to be a hash of header names and values.
+ # This allows you to easily return a response that consists only of
+ # significant headers:
+ #
+ # head :created, :location => person_path(@person)
+ #
+ # It can also be used to return exceptional conditions:
+ #
+ # return head(:method_not_allowed) unless request.post?
+ # return head(:bad_request) unless valid_request?
+ # render
+ def head(status, options = {})
+ options, status = status, nil if status.is_a?(Hash)
+ status ||= options.delete(:status) || :ok
+ location = options.delete(:location)
+
+ options.each do |key, value|
+ headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s
+ end
+
+ render :nothing => true, :status => status, :location => location
+ end
+ end
+end
\ No newline at end of file
--
cgit v1.2.3
From e1786ee6ebee9fab10d6756be1eeacbbe6b65b48 Mon Sep 17 00:00:00 2001
From: Yehuda Katz
Date: Mon, 26 Oct 2009 17:32:42 -0700
Subject: Fixes expires_now and cleans things up a bit
---
.../lib/action_controller/metal/conditional_get.rb | 2 +-
actionpack/lib/action_dispatch/http/request.rb | 21 +++++++++------------
actionpack/lib/action_dispatch/http/response.rb | 2 ++
actionpack/test/controller/render_test.rb | 10 ++++++++++
4 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 52f5a9727e..5156fbc1d5 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -80,7 +80,7 @@ module ActionController
# Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or
# intermediate caches (like caching proxy servers).
def expires_now #:doc:
- response.headers["Cache-Control"] = "no-cache"
+ response.cache_control.replace(:no_cache => true)
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index bff030f0e4..1e366520c9 100755
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -136,19 +136,16 @@ module ActionDispatch
# If-Modified-Since and If-None-Match conditions. If both headers are
# supplied, both must match, or the request is not considered fresh.
def fresh?(response)
- case
- when if_modified_since && if_none_match
- not_modified?(response.last_modified) && etag_matches?(response.etag)
- when if_modified_since
- not_modified?(response.last_modified)
- when if_none_match
- etag_matches?(response.etag)
- else
- false
- end
- end
+ last_modified = if_modified_since
+ etag = if_none_match
- ONLY_ALL = [Mime::ALL].freeze
+ return false unless last_modified || etag
+
+ success = true
+ success &&= not_modified?(response.last_modified) if last_modified
+ success &&= etag_matches?(response.etag) if etag
+ success
+ end
# Returns the Mime type for the \format used in the request.
#
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 3e3b473178..b3ed7c9d1a 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -270,6 +270,8 @@ module ActionDispatch # :nodoc:
if control.empty?
headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
+ elsif @cache_control[:no_cache]
+ headers["Cache-Control"] = "no-cache"
else
extras = control[:extras]
max_age = control[:max_age]
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 2db524ca4b..ac8dad7c42 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -73,6 +73,11 @@ class TestController < ActionController::Base
render :action => 'hello_world'
end
+ def conditional_hello_with_expires_now
+ expires_now
+ render :action => 'hello_world'
+ end
+
def conditional_hello_with_bangs
render :action => 'hello_world'
end
@@ -1321,6 +1326,11 @@ class ExpiresInRenderTest < ActionController::TestCase
get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
end
+
+ def test_expires_now
+ get :conditional_hello_with_expires_now
+ assert_equal "no-cache", @response.headers["Cache-Control"]
+ end
end
--
cgit v1.2.3
From 000d5936216f363a5b11013f664959019b7ebac2 Mon Sep 17 00:00:00 2001
From: Yehuda Katz
Date: Mon, 26 Oct 2009 18:01:09 -0700
Subject: Clean up and update cookies
---
actionpack/lib/action_controller/metal/cookies.rb | 44 ++++++++++++++---------
actionpack/test/controller/cookie_test.rb | 11 +++---
2 files changed, 32 insertions(+), 23 deletions(-)
diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb
index c328db8beb..6855ca1478 100644
--- a/actionpack/lib/action_controller/metal/cookies.rb
+++ b/actionpack/lib/action_controller/metal/cookies.rb
@@ -44,24 +44,31 @@ module ActionController #:nodoc:
# * :httponly - Whether this cookie is accessible via scripting or
# only HTTP. Defaults to +false+.
module Cookies
- def self.included(base)
- base.helper_method :cookies
+ extend ActiveSupport::Concern
+
+ include RackConvenience
+
+ included do
+ helper_method :cookies
end
- protected
- # Returns the cookie container, which operates as described above.
- def cookies
- @cookies ||= CookieJar.new(self)
- end
+ protected
+ # Returns the cookie container, which operates as described above.
+ def cookies
+ @cookies ||= CookieJar.build(request, response)
+ end
end
class CookieJar < Hash #:nodoc:
- def initialize(controller)
- @controller, @cookies = controller, controller.request.cookies
- super()
- update(@cookies)
+ def self.build(request, response)
+ new.tap do |hash|
+ hash.update(request.cookies)
+ hash.response = response
+ end
end
+ attr_accessor :response
+
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
def [](name)
super(name.to_s)
@@ -72,13 +79,16 @@ module ActionController #:nodoc:
def []=(key, options)
if options.is_a?(Hash)
options.symbolize_keys!
+ value = options[:value]
else
- options = { :value => options }
+ value = options
+ options = { :value => value }
end
- options[:path] = "/" unless options.has_key?(:path)
- super(key.to_s, options[:value])
- @controller.response.set_cookie(key, options)
+ super(key.to_s, value)
+
+ options[:path] ||= "/"
+ response.set_cookie(key, options)
end
# Removes the cookie on the client machine by setting the value to an empty string
@@ -86,9 +96,9 @@ module ActionController #:nodoc:
# an options hash to delete cookies with extra data such as a :path.
def delete(key, options = {})
options.symbolize_keys!
- options[:path] = "/" unless options.has_key?(:path)
+ options[:path] ||= "/"
value = super(key.to_s)
- @controller.response.delete_cookie(key, options)
+ response.delete_cookie(key, options)
value
end
end
diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb
index b429cbf0e6..53d4364576 100644
--- a/actionpack/test/controller/cookie_test.rb
+++ b/actionpack/test/controller/cookie_test.rb
@@ -106,7 +106,7 @@ class CookieTest < ActionController::TestCase
def test_cookiejar_accessor
@request.cookies["user_name"] = "david"
@controller.request = @request
- jar = ActionController::CookieJar.new(@controller)
+ jar = ActionController::CookieJar.build(@controller.request, @controller.response)
assert_equal "david", jar["user_name"]
assert_equal nil, jar["something_else"]
end
@@ -114,14 +114,14 @@ class CookieTest < ActionController::TestCase
def test_cookiejar_accessor_with_array_value
@request.cookies["pages"] = %w{1 2 3}
@controller.request = @request
- jar = ActionController::CookieJar.new(@controller)
+ jar = ActionController::CookieJar.build(@controller.request, @controller.response)
assert_equal %w{1 2 3}, jar["pages"]
end
def test_cookiejar_delete_removes_item_and_returns_its_value
@request.cookies["user_name"] = "david"
@controller.response = @response
- jar = ActionController::CookieJar.new(@controller)
+ jar = ActionController::CookieJar.build(@controller.request, @controller.response)
assert_equal "david", jar.delete("user_name")
end
@@ -131,9 +131,8 @@ class CookieTest < ActionController::TestCase
end
def test_cookies_persist_throughout_request
- get :authenticate
- cookies = @controller.send(:cookies)
- assert_equal 'david', cookies['user_name']
+ response = get :authenticate
+ assert response.headers["Set-Cookie"] =~ /user_name=david/
end
private
--
cgit v1.2.3
From 2bdd8fa86313a48de11d95fc48f97ada24d7d8af Mon Sep 17 00:00:00 2001
From: Yehuda Katz
Date: Mon, 26 Oct 2009 21:10:40 -0700
Subject: Clean up parameter logging some
---
.../metal/filter_parameter_logging.rb | 65 ++++++++--------------
actionpack/test/controller/filter_params_test.rb | 18 +++---
2 files changed, 32 insertions(+), 51 deletions(-)
diff --git a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
index 4259d9de19..a53c052075 100644
--- a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
+++ b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
@@ -4,10 +4,6 @@ module ActionController
include AbstractController::Logger
- included do
- include InstanceMethodsForNewBase
- end
-
module ClassMethods
# Replace sensitive parameter data from the request log.
# Filters parameters that have any of the arguments as a substring.
@@ -17,8 +13,6 @@ module ActionController
# can be replaced using String#replace or similar method.
#
# Examples:
- # filter_parameter_logging
- # => Does nothing, just slows the logging process down
#
# filter_parameter_logging :password
# => replaces the value to all keys matching /password/i with "[FILTERED]"
@@ -33,64 +27,51 @@ module ActionController
# => 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)
- parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0
+ 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 |unfiltered_parameters|
- filtered_parameters = {}
+ define_method(:filter_parameters) do |original_params|
+ filtered_params = {}
- unfiltered_parameters.each do |key, value|
+ original_params.each do |key, value|
if key =~ parameter_filter
- filtered_parameters[key] = '[FILTERED]'
+ value = '[FILTERED]'
elsif value.is_a?(Hash)
- filtered_parameters[key] = filter_parameters(value)
+ value = filter_parameters(value)
elsif value.is_a?(Array)
- filtered_parameters[key] = value.collect do |item|
- filter_parameters(item)
- end
+ value = value.map { |item| filter_parameters(item) }
elsif block_given?
key = key.dup
value = value.dup if value.duplicable?
yield key, value
- filtered_parameters[key] = value
- else
- filtered_parameters[key] = value
end
+
+ filtered_params[key] = value
end
- filtered_parameters
+ filtered_params
end
protected :filter_parameters
end
end
- module InstanceMethodsForNewBase
- # TODO : Fix the order of information inside such that it's exactly same as the old base
- def process(*)
- ret = super
-
- if logger
- parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
- parameters = parameters.except!(:controller, :action, :format, :_method, :only_path)
+ INTERNAL_PARAMS = [:controller, :action, :format, :_method, :only_path]
- unless parameters.empty?
- # TODO : Move DelayedLog to AS
- log = AbstractController::Logger::DelayedLog.new { " Parameters: #{parameters.inspect}" }
- logger.info(log)
- end
- end
-
- ret
+ def process(*)
+ response = super
+ if logger
+ parameters = filter_parameters(params).except!(*INTERNAL_PARAMS)
+ logger.info { " Parameters: #{parameters.inspect}" } unless parameters.empty?
end
+ response
end
- private
+ protected
- # TODO : This method is not needed for the new base
- def log_processing_for_parameters
- parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
- parameters = parameters.except!(:controller, :action, :format, :_method)
-
- logger.info " Parameters: #{parameters.inspect}" unless parameters.empty?
+ def filter_parameters(params)
+ params.dup
end
+
end
end
diff --git a/actionpack/test/controller/filter_params_test.rb b/actionpack/test/controller/filter_params_test.rb
index 19232c6bc9..43bef34885 100644
--- a/actionpack/test/controller/filter_params_test.rb
+++ b/actionpack/test/controller/filter_params_test.rb
@@ -19,23 +19,23 @@ class FilterParamTest < ActionController::TestCase
def method_missing(method, *args)
@logged ||= []
- @logged << args.first
+ @logged << args.first unless block_given?
+ @logged << yield if block_given?
end
end
setup :set_logger
+ 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)
- assert !@controller.respond_to?(:filter_parameters)
-
- FilterParamController.filter_parameter_logging
- assert @controller.respond_to?(:filter_parameters)
- test_hashes = [[{},{},[]],
- [{'foo'=>nil},{'foo'=>nil},[]],
- [{'foo'=>'bar'},{'foo'=>'bar'},[]],
- [{'foo'=>1},{'foo'=>1},[]],
+ 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'],
--
cgit v1.2.3
From df06e0bd86bc054fc4e3d78193c09da9e27971a4 Mon Sep 17 00:00:00 2001
From: Yehuda Katz
Date: Mon, 26 Oct 2009 23:11:52 -0700
Subject: Clean up flash a bit
---
actionpack/lib/action_controller/metal/flash.rb | 48 ++++++++++++-------------
1 file changed, 23 insertions(+), 25 deletions(-)
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index 590f9be3ac..b2fc359df4 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -49,7 +49,7 @@ module ActionController #:nodoc:
class FlashHash < Hash
def initialize #:nodoc:
super
- @used = {}
+ @used = Set.new
end
def []=(k, v) #:nodoc:
@@ -65,7 +65,7 @@ module ActionController #:nodoc:
alias :merge! :update
def replace(h) #:nodoc:
- @used = {}
+ @used = Set.new
super
end
@@ -104,8 +104,8 @@ module ActionController #:nodoc:
# This method is called automatically by filters, so you generally don't need to care about it.
def sweep #:nodoc:
keys.each do |k|
- unless @used[k]
- use(k)
+ unless @used.include?(k)
+ @used << k
else
delete(k)
@used.delete(k)
@@ -113,47 +113,45 @@ module ActionController #:nodoc:
end
# clean up after keys that could have been left over by calling reject! or shift on the flash
- (@used.keys - keys).each{ |k| @used.delete(k) }
+ (@used - keys).each{ |k| @used.delete(k) }
end
- def store(session, key = "flash")
+ def store(session)
return if self.empty?
- session[key] = self
+ session["flash"] = self
end
- private
- # Used internally by the keep and discard methods
- # use() # marks the entire flash as used
- # use('msg') # marks the "msg" entry as used
- # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
- # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
- # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
- # if no key is passed.
- def use(key = nil, used = true)
- Array(key || keys).each { |k| @used[k] = used }
- return key ? self[key] : self
- end
+ private
+ # Used internally by the keep and discard methods
+ # use() # marks the entire flash as used
+ # use('msg') # marks the "msg" entry as used
+ # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
+ # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
+ # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
+ # if no key is passed.
+ def use(key = nil, used = true)
+ Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
+ return key ? self[key] : self
+ end
end
protected
def process_action(method_name)
super
- if defined? @_flash
- @_flash.store(session)
- remove_instance_variable(:@_flash)
- end
+ @_flash.store(session) if @_flash
+ @_flash = nil
end
def reset_session
super
- remove_instance_variable(:@_flash) if defined?(@_flash)
+ @_flash = nil
end
# Access the contents of the flash. Use flash["notice"] to
# read a notice you put there or flash["notice"] = "hello"
# to put a new one.
def flash #:doc:
- if !defined?(@_flash)
+ if !@_flash
@_flash = session["flash"] || FlashHash.new
@_flash.sweep
end
--
cgit v1.2.3
From b3012bbd1637e61c628fc700f885ab4a06251175 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Tue, 27 Oct 2009 01:55:17 -0700
Subject: Tweak CI for toplevel gem bundle
---
ci/ci_build.rb | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/ci/ci_build.rb b/ci/ci_build.rb
index c49fb5f0c4..23e223b1b2 100755
--- a/ci/ci_build.rb
+++ b/ci/ci_build.rb
@@ -16,6 +16,14 @@ root_dir = File.expand_path(File.dirname(__FILE__) + "/..")
# A security hole, but there is nothing valuable on rails CI box anyway.
build_results[:geminstaller] = system "sudo geminstaller --config=#{root_dir}/ci/geminstaller.yml --exceptions"
+rm_f "#{root_dir}/vendor"
+cd root_dir do
+ puts
+ puts "[CruiseControl] Bundling RubyGems"
+ puts
+ build_results[:bundle] = system 'gem bundle'
+end
+
cd "#{root_dir}/activesupport" do
puts
puts "[CruiseControl] Building ActiveSupport"
@@ -29,28 +37,28 @@ cd "#{root_dir}/activerecord" do
puts
puts "[CruiseControl] Building ActiveRecord with MySQL"
puts
- build_results[:activerecord_mysql] = system 'gem bundle && rake mysql:rebuild_databases && rake test_mysql'
+ build_results[:activerecord_mysql] = system 'rake mysql:rebuild_databases && rake test_mysql'
end
cd "#{root_dir}/activerecord" do
puts
puts "[CruiseControl] Building ActiveRecord with PostgreSQL"
puts
- build_results[:activerecord_postgresql8] = system 'gem bundle && rake postgresql:rebuild_databases && rake test_postgresql'
+ build_results[:activerecord_postgresql8] = system 'rake postgresql:rebuild_databases && rake test_postgresql'
end
cd "#{root_dir}/activerecord" do
puts
puts "[CruiseControl] Building ActiveRecord with SQLite 3"
puts
- build_results[:activerecord_sqlite3] = system 'gem bundle && rake test_sqlite3'
+ build_results[:activerecord_sqlite3] = system 'rake test_sqlite3'
end
cd "#{root_dir}/activemodel" do
puts
puts "[CruiseControl] Building ActiveModel"
puts
- build_results[:activemodel] = system 'gem bundle && rake'
+ build_results[:activemodel] = system 'rake'
end
rm_f "#{root_dir}/activeresource/debug.log"
@@ -65,7 +73,7 @@ cd "#{root_dir}/actionpack" do
puts
puts "[CruiseControl] Building ActionPack"
puts
- build_results[:actionpack] = system 'gem bundle && rake'
+ build_results[:actionpack] = system 'rake'
build_results[:actionpack_isolated] = system 'rake test:isolated'
end
@@ -73,14 +81,14 @@ cd "#{root_dir}/actionmailer" do
puts
puts "[CruiseControl] Building ActionMailer"
puts
- build_results[:actionmailer] = system 'gem bundle && rake'
+ build_results[:actionmailer] = system 'rake'
end
cd "#{root_dir}/railties" do
puts
puts "[CruiseControl] Building RailTies"
puts
- build_results[:railties] = system 'gem bundle && rake'
+ build_results[:railties] = system 'rake'
end
--
cgit v1.2.3
From 58555d0c8b8beaee02ad72cfaf262dbf21674066 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Tue, 27 Oct 2009 02:03:25 -0700
Subject: Remove old per-component bundled environments first
---
ci/ci_build.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/ci/ci_build.rb b/ci/ci_build.rb
index 23e223b1b2..b2f9f59bec 100755
--- a/ci/ci_build.rb
+++ b/ci/ci_build.rb
@@ -17,6 +17,7 @@ root_dir = File.expand_path(File.dirname(__FILE__) + "/..")
build_results[:geminstaller] = system "sudo geminstaller --config=#{root_dir}/ci/geminstaller.yml --exceptions"
rm_f "#{root_dir}/vendor"
+system "rm -rf #{root_dir}/*/vendor"
cd root_dir do
puts
puts "[CruiseControl] Bundling RubyGems"
--
cgit v1.2.3
From 4653719aa6407f7d0288210204c7f3d8f2728489 Mon Sep 17 00:00:00 2001
From: Yehuda Katz
Date: Mon, 26 Oct 2009 23:11:52 -0700
Subject: Clean up flash a bit
---
actionpack/lib/action_controller/metal/flash.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index b2fc359df4..f43900faa0 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -151,7 +151,7 @@ module ActionController #:nodoc:
# read a notice you put there or flash["notice"] = "hello"
# to put a new one.
def flash #:doc:
- if !@_flash
+ unless @_flash
@_flash = session["flash"] || FlashHash.new
@_flash.sweep
end
--
cgit v1.2.3
From b3a198041befa933a26a597451f84482df268d0f Mon Sep 17 00:00:00 2001
From: Yehuda Katz
Date: Tue, 27 Oct 2009 00:07:21 -0700
Subject: Some optimizations on AS::Notifications. This does not change the
public-facing API.
---
activesupport/lib/active_support/notifications.rb | 62 ++++++++++++-----------
activesupport/test/notifications_test.rb | 29 +++++++----
2 files changed, 51 insertions(+), 40 deletions(-)
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 7e9ffca13f..8e705a4eaa 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -62,15 +62,14 @@ module ActiveSupport
class Instrumenter
def initialize(publisher)
@publisher = publisher
+ @id = SecureRandom.hex(10)
end
def instrument(name, payload={})
- payload[:time] = Time.now
- payload[:thread_id] = Thread.current.object_id
- payload[:result] = yield if block_given?
+ time = Time.now
+ result = yield if block_given?
ensure
- payload[:duration] = 1000 * (Time.now.to_f - payload[:time].to_f)
- @publisher.publish(name, payload)
+ @publisher.publish(name, time, Time.now, result, @id, payload)
end
end
@@ -79,8 +78,8 @@ module ActiveSupport
@queue = queue
end
- def publish(name, payload)
- @queue.publish(name, payload)
+ def publish(*args)
+ @queue.publish(*args)
end
end
@@ -95,27 +94,31 @@ module ActiveSupport
end
def subscribe
- @queue.subscribe(@pattern) do |name, payload|
- yield Event.new(name, payload)
+ @queue.subscribe(@pattern) do |*args|
+ yield Event.new(*args)
end
end
end
class Event
- attr_reader :name, :time, :duration, :thread_id, :result, :payload
+ attr_reader :name, :time, :end, :thread_id, :result, :payload
- def initialize(name, payload)
+ def initialize(name, start, ending, result, thread_id, payload)
@name = name
@payload = payload.dup
- @time = @payload.delete(:time)
- @thread_id = @payload.delete(:thread_id)
- @result = @payload.delete(:result)
- @duration = @payload.delete(:duration)
+ @time = start
+ @thread_id = thread_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 + self.duration >= event.duration)
+ start <= 0 && (start + duration >= event.duration)
end
end
@@ -124,12 +127,13 @@ module ActiveSupport
#
class LittleFanout
def initialize
- @listeners, @stream = [], Queue.new
- @thread = Thread.new { consume }
+ @listeners = []
+ @stream = Queue.new
+ Thread.new { consume }
end
- def publish(*event)
- @stream.push(event)
+ def publish(*args)
+ @stream.push(args)
end
def subscribe(pattern=nil, &block)
@@ -137,30 +141,30 @@ module ActiveSupport
end
def consume
- while event = @stream.shift
- @listeners.each { |l| l.publish(*event) }
+ while args = @stream.shift
+ @listeners.each { |l| l.publish(*args) }
end
end
class Listener
- attr_reader :thread
+ # attr_reader :thread
def initialize(pattern, &block)
@pattern = pattern
@subscriber = block
@queue = Queue.new
- @thread = Thread.new { consume }
+ Thread.new { consume }
end
- def publish(name, payload)
- unless @pattern && !(@pattern === name.to_s)
- @queue << [name, payload]
+ def publish(name, *args)
+ if !@pattern || @pattern === name.to_s
+ @queue << args.unshift(name)
end
end
def consume
- while event = @queue.shift
- @subscriber.call(*event)
+ while args = @queue.shift
+ @subscriber.call(*args)
end
end
end
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index 561ee2b0ba..7d2bdf5ccf 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -8,25 +8,28 @@ class ActiveSupport::Notifications::LittleFanout
end
class NotificationsEventTest < Test::Unit::TestCase
- def test_events_are_initialized_with_name_and_payload
- event = event(:foo, :payload => :bar)
+ 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
- event = event(:foo, :time => (time = Time.now), :result => 1, :duration => 10)
+ 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, event.duration
+ assert_equal 10.0, event.duration
end
def test_event_is_parent_based_on_time_frame
- parent = event(:foo, :time => Time.utc(2009), :duration => 10000)
- child = event(:foo, :time => Time.utc(2009, 01, 01, 0, 0, 1), :duration => 1000)
- not_child = event(:foo, :time => Time.utc(2009, 01, 01, 0, 0, 1), :duration => 10000)
+ 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)
@@ -34,11 +37,15 @@ class NotificationsEventTest < Test::Unit::TestCase
assert !not_child.parent_of?(parent)
end
- protected
+protected
- def event(*args)
- ActiveSupport::Notifications::Event.new(*args)
- end
+ def random_id
+ @random_id ||= ActiveSupport::SecureRandom.hex(10)
+ end
+
+ def event(*args)
+ ActiveSupport::Notifications::Event.new(*args)
+ end
end
class NotificationsMainTest < Test::Unit::TestCase
--
cgit v1.2.3
From c3fa20883e667ce2e7ce207ce8f08ba64f7dcf23 Mon Sep 17 00:00:00 2001
From: Yehuda Katz
Date: Tue, 27 Oct 2009 09:21:01 -0700
Subject: #include should be #extend
---
actionpack/lib/action_controller/metal/session_management.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb
index 9f4c4b8b39..d70f40ce7a 100644
--- a/actionpack/lib/action_controller/metal/session_management.rb
+++ b/actionpack/lib/action_controller/metal/session_management.rb
@@ -1,6 +1,6 @@
module ActionController #:nodoc:
module SessionManagement #:nodoc:
- include ActiveSupport::Concern
+ extend ActiveSupport::Concern
include ActionController::Configuration
--
cgit v1.2.3
From b30294b54ab019b0e53402c6927981f8c306e976 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Tue, 27 Oct 2009 09:34:17 -0700
Subject: Fix broken tests
---
activesupport/lib/active_support/notifications.rb | 1 +
railties/test/application/notifications_test.rb | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 8e705a4eaa..139c66b1e1 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -1,6 +1,7 @@
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
diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb
index 83c18be057..62ed4f4ad4 100644
--- a/railties/test/application/notifications_test.rb
+++ b/railties/test/application/notifications_test.rb
@@ -12,7 +12,7 @@ module ApplicationTests
@subscribers = []
end
- def publish(name, payload=nil)
+ def publish(name, *args)
@events << name
end
--
cgit v1.2.3
From c28a45ad8bb72896d84285158e7f5dc728933062 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?=
Date: Wed, 21 Oct 2009 11:12:30 -0200
Subject: Fix rake dev and update vendored Thor.
Signed-off-by: Yehuda Katz
---
railties/Rakefile | 4 +-
railties/lib/rails/generators.rb | 2 +-
.../lib/rails/vendor/thor-0.11.6/CHANGELOG.rdoc | 77 ---
railties/lib/rails/vendor/thor-0.11.6/LICENSE | 20 -
railties/lib/rails/vendor/thor-0.11.6/README.rdoc | 234 ----------
.../lib/rails/vendor/thor-0.11.6/bin/rake2thor | 87 ----
railties/lib/rails/vendor/thor-0.11.6/bin/thor | 7 -
railties/lib/rails/vendor/thor-0.11.6/lib/thor.rb | 243 ----------
.../rails/vendor/thor-0.11.6/lib/thor/actions.rb | 273 -----------
.../thor-0.11.6/lib/thor/actions/create_file.rb | 102 ----
.../thor-0.11.6/lib/thor/actions/directory.rb | 87 ----
.../lib/thor/actions/empty_directory.rb | 133 ------
.../lib/thor/actions/file_manipulation.rb | 219 ---------
.../lib/thor/actions/inject_into_file.rb | 101 ----
.../lib/rails/vendor/thor-0.11.6/lib/thor/base.rb | 517 ---------------------
.../thor/core_ext/hash_with_indifferent_access.rb | 75 ---
.../thor-0.11.6/lib/thor/core_ext/ordered_hash.rb | 100 ----
.../lib/rails/vendor/thor-0.11.6/lib/thor/error.rb | 27 --
.../lib/rails/vendor/thor-0.11.6/lib/thor/group.rb | 263 -----------
.../vendor/thor-0.11.6/lib/thor/invocation.rb | 178 -------
.../rails/vendor/thor-0.11.6/lib/thor/parser.rb | 4 -
.../vendor/thor-0.11.6/lib/thor/parser/argument.rb | 67 ---
.../thor-0.11.6/lib/thor/parser/arguments.rb | 145 ------
.../vendor/thor-0.11.6/lib/thor/parser/option.rb | 132 ------
.../vendor/thor-0.11.6/lib/thor/parser/options.rb | 142 ------
.../vendor/thor-0.11.6/lib/thor/rake_compat.rb | 67 ---
.../rails/vendor/thor-0.11.6/lib/thor/runner.rb | 299 ------------
.../lib/rails/vendor/thor-0.11.6/lib/thor/shell.rb | 72 ---
.../vendor/thor-0.11.6/lib/thor/shell/basic.rb | 219 ---------
.../vendor/thor-0.11.6/lib/thor/shell/color.rb | 108 -----
.../lib/rails/vendor/thor-0.11.6/lib/thor/task.rb | 122 -----
.../lib/rails/vendor/thor-0.11.6/lib/thor/util.rb | 251 ----------
.../lib/rails/vendor/thor-0.11.8/CHANGELOG.rdoc | 77 +++
railties/lib/rails/vendor/thor-0.11.8/LICENSE | 20 +
railties/lib/rails/vendor/thor-0.11.8/README.rdoc | 234 ++++++++++
railties/lib/rails/vendor/thor-0.11.8/Thorfile | 63 +++
railties/lib/rails/vendor/thor-0.11.8/lib/thor.rb | 242 ++++++++++
.../rails/vendor/thor-0.11.8/lib/thor/actions.rb | 273 +++++++++++
.../thor-0.11.8/lib/thor/actions/create_file.rb | 102 ++++
.../thor-0.11.8/lib/thor/actions/directory.rb | 89 ++++
.../lib/thor/actions/empty_directory.rb | 133 ++++++
.../lib/thor/actions/file_manipulation.rb | 219 +++++++++
.../lib/thor/actions/inject_into_file.rb | 101 ++++
.../lib/rails/vendor/thor-0.11.8/lib/thor/base.rb | 517 +++++++++++++++++++++
.../thor/core_ext/hash_with_indifferent_access.rb | 75 +++
.../thor-0.11.8/lib/thor/core_ext/ordered_hash.rb | 100 ++++
.../lib/rails/vendor/thor-0.11.8/lib/thor/error.rb | 27 ++
.../lib/rails/vendor/thor-0.11.8/lib/thor/group.rb | 263 +++++++++++
.../vendor/thor-0.11.8/lib/thor/invocation.rb | 178 +++++++
.../rails/vendor/thor-0.11.8/lib/thor/parser.rb | 4 +
.../vendor/thor-0.11.8/lib/thor/parser/argument.rb | 67 +++
.../thor-0.11.8/lib/thor/parser/arguments.rb | 145 ++++++
.../vendor/thor-0.11.8/lib/thor/parser/option.rb | 132 ++++++
.../vendor/thor-0.11.8/lib/thor/parser/options.rb | 142 ++++++
.../vendor/thor-0.11.8/lib/thor/rake_compat.rb | 66 +++
.../rails/vendor/thor-0.11.8/lib/thor/runner.rb | 299 ++++++++++++
.../lib/rails/vendor/thor-0.11.8/lib/thor/shell.rb | 78 ++++
.../vendor/thor-0.11.8/lib/thor/shell/basic.rb | 219 +++++++++
.../vendor/thor-0.11.8/lib/thor/shell/color.rb | 108 +++++
.../lib/rails/vendor/thor-0.11.8/lib/thor/task.rb | 122 +++++
.../lib/rails/vendor/thor-0.11.8/lib/thor/util.rb | 251 ++++++++++
.../rails/vendor/thor-0.11.8/lib/thor/version.rb | 3 +
62 files changed, 4352 insertions(+), 4374 deletions(-)
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/CHANGELOG.rdoc
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/LICENSE
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/README.rdoc
delete mode 100755 railties/lib/rails/vendor/thor-0.11.6/bin/rake2thor
delete mode 100755 railties/lib/rails/vendor/thor-0.11.6/bin/thor
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/create_file.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/directory.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/empty_directory.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/file_manipulation.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/inject_into_file.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/base.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/hash_with_indifferent_access.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/ordered_hash.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/error.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/group.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/invocation.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/argument.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/arguments.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/option.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/options.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/rake_compat.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/runner.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell/basic.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell/color.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/task.rb
delete mode 100644 railties/lib/rails/vendor/thor-0.11.6/lib/thor/util.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/CHANGELOG.rdoc
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/LICENSE
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/README.rdoc
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/Thorfile
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/create_file.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/directory.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/empty_directory.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/file_manipulation.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/inject_into_file.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/base.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/core_ext/hash_with_indifferent_access.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/core_ext/ordered_hash.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/error.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/group.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/invocation.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/argument.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/arguments.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/option.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/options.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/rake_compat.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/runner.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/shell.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/shell/basic.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/shell/color.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/task.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/util.rb
create mode 100644 railties/lib/rails/vendor/thor-0.11.8/lib/thor/version.rb
diff --git a/railties/Rakefile b/railties/Rakefile
index 0ba5ca10c2..ebac90a403 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -6,7 +6,8 @@ require 'rake/gempackagetask'
require 'date'
require 'rbconfig'
-require File.join(File.dirname(__FILE__), 'lib/rails', 'version')
+$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/lib"
+require 'rails'
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
PKG_NAME = ENV['PKG_NAME'] || 'rails'
@@ -81,7 +82,6 @@ end
# Run application generator -------------------------------------------------------------
task :create_rails do
- $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/lib"
require 'rails/generators'
require 'rails/generators/rails/app/app_generator'
Rails::Generators::AppGenerator.start [ File.basename(PKG_DESTINATION), "--quiet" ],
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index d1bdbd5253..49f32aa0db 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -9,7 +9,7 @@ require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/inflections'
# TODO: Do not always push on vendored thor
-$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.11.6/lib")
+$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.11.8/lib")
require 'rails/generators/base'
require 'rails/generators/named_base'
diff --git a/railties/lib/rails/vendor/thor-0.11.6/CHANGELOG.rdoc b/railties/lib/rails/vendor/thor-0.11.6/CHANGELOG.rdoc
deleted file mode 100644
index dba25b7205..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/CHANGELOG.rdoc
+++ /dev/null
@@ -1,77 +0,0 @@
-== TODO
-
-* Improve spec coverage for Thor::Runner
-
-== 0.11.x, released 2009-07-01
-
-* Added a rake compatibility layer. It allows you to use spec and rdoc tasks on
- Thor classes.
-
-* BACKWARDS INCOMPATIBLE: aliases are not generated automatically anymore
- since it wrong behavior to the invocation system.
-
-* thor help now show information about any class/task. All those calls are
- possible:
-
- thor help describe
- thor help describe:amazing
-
- Or even with default namespaces:
-
- thor help :spec
-
-* Thor::Runner now invokes the default task if none is supplied:
-
- thor describe # invokes the default task, usually help
-
-* Thor::Runner now works with mappings:
-
- thor describe -h
-
-* Added some documentation and code refactoring.
-
-== 0.9.8, released 2008-10-20
-
-* Fixed some tiny issues that were introduced lately.
-
-== 0.9.7, released 2008-10-13
-
-* Setting global method options on the initialize method works as expected:
- All other tasks will accept these global options in addition to their own.
-* Added 'group' notion to Thor task sets (class Thor); by default all tasks
- are in the 'standard' group. Running 'thor -T' will only show the standard
- tasks - adding --all will show all tasks. You can also filter on a specific
- group using the --group option: thor -T --group advanced
-
-== 0.9.6, released 2008-09-13
-
-* Generic improvements
-
-== 0.9.5, released 2008-08-27
-
-* Improve Windows compatibility
-* Update (incorrect) README and task.thor sample file
-* Options hash is now frozen (once returned)
-* Allow magic predicates on options object. For instance: `options.force?`
-* Add support for :numeric type
-* BACKWARDS INCOMPATIBLE: Refactor Thor::Options. You cannot access shorthand forms in options hash anymore (for instance, options[:f])
-* Allow specifying optional args with default values: method_options(:user => "mislav")
-* Don't write options for nil or false values. This allows, for example, turning color off when running specs.
-* Exit with the status of the spec command to help CI stuff out some.
-
-== 0.9.4, released 2008-08-13
-
-* Try to add Windows compatibility.
-* BACKWARDS INCOMPATIBLE: options hash is now accessed as a property in your class and is not passed as last argument anymore
-* Allow options at the beginning of the argument list as well as the end.
-* Make options available with symbol keys in addition to string keys.
-* Allow true to be passed to Thor#method_options to denote a boolean option.
-* If loading a thor file fails, don't give up, just print a warning and keep going.
-* Make sure that we re-raise errors if they happened further down the pipe than we care about.
-* Only delete the old file on updating when the installation of the new one is a success
-* Make it Ruby 1.8.5 compatible.
-* Don't raise an error if a boolean switch is defined multiple times.
-* Thor::Options now doesn't parse through things that look like options but aren't.
-* Add URI detection to install task, and make sure we don't append ".thor" to URIs
-* Add rake2thor to the gem binfiles.
-* Make sure local Thorfiles override system-wide ones.
diff --git a/railties/lib/rails/vendor/thor-0.11.6/LICENSE b/railties/lib/rails/vendor/thor-0.11.6/LICENSE
deleted file mode 100644
index 98722da459..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2008 Yehuda Katz
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/railties/lib/rails/vendor/thor-0.11.6/README.rdoc b/railties/lib/rails/vendor/thor-0.11.6/README.rdoc
deleted file mode 100644
index f1106f02b6..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/README.rdoc
+++ /dev/null
@@ -1,234 +0,0 @@
-= thor
-
-Map options to a class. Simply create a class with the appropriate annotations
-and have options automatically map to functions and parameters.
-
-Example:
-
- class App < Thor # [1]
- map "-L" => :list # [2]
-
- desc "install APP_NAME", "install one of the available apps" # [3]
- method_options :force => :boolean, :alias => :string # [4]
- def install(name)
- user_alias = options[:alias]
- if options.force?
- # do something
- end
- # other code
- end
-
- desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
- def list(search="")
- # list everything
- end
- end
-
-Thor automatically maps commands as such:
-
- thor app:install myname --force
-
-That gets converted to:
-
- App.new.install("myname")
- # with {'force' => true} as options hash
-
-1. Inherit from Thor to turn a class into an option mapper
-2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list
-3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description
-4. Provide any additional options that will be available the instance method options.
-
-== Types for method_options
-
-* :boolean - is parsed as --option or --option=true
-* :string - is parsed as --option=VALUE
-* :numeric - is parsed as --option=N
-* :array - is parsed as --option=one two three
-* :hash - is parsed as --option=name:string age:integer
-
-Besides, method_option allows a default value to be given, examples:
-
- method_options :force => false
- #=> Creates a boolean option with default value false
-
- method_options :alias => "bar"
- #=> Creates a string option with default value "bar"
-
- method_options :threshold => 3.0
- #=> Creates a numeric option with default value 3.0
-
-You can also supply :option => :required to mark an option as required. The
-type is assumed to be string. If you want a required hash with default values
-as option, you can use method_option which uses a more declarative style:
-
- method_option :attributes, :type => :hash, :default => {}, :required => true
-
-All arguments can be set to nil (except required arguments), by suppling a no or
-skip variant. For example:
-
- thor app name --no-attributes
-
-In previous versions, aliases for options were created automatically, but now
-they should be explicit. You can supply aliases in both short and declarative
-styles:
-
- method_options %w( force -f ) => :boolean
-
-Or:
-
- method_option :force, :type => :boolean, :aliases => "-f"
-
-You can supply as many aliases as you want.
-
-NOTE: Type :optional available in Thor 0.9.0 was deprecated. Use :string or :boolean instead.
-
-== Namespaces
-
-By default, your Thor tasks are invoked using Ruby namespace. In the example
-above, tasks are invoked as:
-
- thor app:install name --force
-
-However, you could namespace your class as:
-
- module Sinatra
- class App < Thor
- # tasks
- end
- end
-
-And then you should invoke your tasks as:
-
- thor sinatra:app:install name --force
-
-If desired, you can change the namespace:
-
- module Sinatra
- class App < Thor
- namespace :myapp
- # tasks
- end
- end
-
-And then your tasks hould be invoked as:
-
- thor myapp:install name --force
-
-== Invocations
-
-Thor comes with a invocation-dependency system as well which allows a task to be
-invoked only once. For example:
-
- class Counter < Thor
- desc "one", "Prints 1, 2, 3"
- def one
- puts 1
- invoke :two
- invoke :three
- end
-
- desc "two", "Prints 2, 3"
- def two
- puts 2
- invoke :three
- end
-
- desc "three", "Prints 3"
- def three
- puts 3
- end
- end
-
-When invoking the task one:
-
- thor counter:one
-
-The output is "1 2 3", which means that the three task was invoked only once.
-You can even invoke tasks from another class, so be sure to check the
-documentation.
-
-== Thor::Group
-
-Thor has a special class called Thor::Group. The main difference to Thor class
-is that it invokes all tasks at once. The example above could be rewritten in
-Thor::Group as this:
-
- class Counter < Thor::Group
- desc "Prints 1, 2, 3"
-
- def one
- puts 1
- end
-
- def two
- puts 2
- end
-
- def three
- puts 3
- end
- end
-
-When invoked:
-
- thor counter
-
-It prints "1 2 3" as well. Notice you should describe (using the method desc)
-only the class and not each task anymore. Thor::Group is a great tool to create
-generators, since you can define several steps which are invoked in the order they
-are defined (Thor::Group is the tool use in generators in Rails 3.0).
-
-Besides, Thor::Group can parse arguments and options as Thor tasks:
-
- class Counter < Thor::Group
- # number will be available as attr_accessor
- argument :number, :type => :numeric, :desc => "The number to start counting"
- desc "Prints the 'number' given upto 'number+2'"
-
- def one
- puts number + 0
- end
-
- def two
- puts number + 1
- end
-
- def three
- puts number + 2
- end
- end
-
-The counter above expects one parameter and has the folling outputs:
-
- thor counter 5
- # Prints "5 6 7"
-
- thor counter 11
- # Prints "11 12 13"
-
-You can also give options to Thor::Group, but instead of using method_option
-and method_options, you should use class_option and class_options.
-Both argument and class_options methods are available to Thor class as well.
-
-== Actions
-
-Thor comes with several actions which helps with script and generator tasks. You
-might be familiar with them since some came from Rails Templates. They are:
-say, ask, yes?, no?, add_file,
-remove_file, copy_file, template, directory,
-inside, run, inject_into_file and a couple more.
-
-To use them, you just need to include Thor::Actions in your Thor classes:
-
- class App < Thor
- include Thor::Actions
- # tasks
- end
-
-Some actions like copy file requires that a class method called source_root is
-defined in your class. This is the directory where your templates should be
-placed. Be sure to check the documentation.
-
-== License
-
-See MIT LICENSE.
diff --git a/railties/lib/rails/vendor/thor-0.11.6/bin/rake2thor b/railties/lib/rails/vendor/thor-0.11.6/bin/rake2thor
deleted file mode 100755
index 50c7410d80..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/bin/rake2thor
+++ /dev/null
@@ -1,87 +0,0 @@
-#!/usr/bin/env ruby
-
-require 'rubygems'
-require 'ruby2ruby'
-require 'parse_tree'
-if Ruby2Ruby::VERSION >= "1.2.0"
- require 'parse_tree_extensions'
-end
-require 'rake'
-
-input = ARGV[0] || 'Rakefile'
-output = ARGV[1] || 'Thorfile'
-
-$requires = []
-
-module Kernel
- def require_with_record(file)
- $requires << file if caller[1] =~ /rake2thor:/
- require_without_record file
- end
- alias_method :require_without_record, :require
- alias_method :require, :require_with_record
-end
-
-load input
-
-@private_methods = []
-
-def file_task_name(name)
- "compile_" + name.gsub('/', '_slash_').gsub('.', '_dot_').gsub(/\W/, '_')
-end
-
-def method_for_task(task)
- file_task = task.is_a?(Rake::FileTask)
- comment = task.instance_variable_get('@comment')
- prereqs = task.instance_variable_get('@prerequisites').select(&Rake::Task.method(:task_defined?))
- actions = task.instance_variable_get('@actions')
- name = task.name.gsub(/^([^:]+:)+/, '')
- name = file_task_name(name) if file_task
- meth = ''
-
- meth << "desc #{name.inspect}, #{comment.inspect}\n" if comment
- meth << "def #{name}\n"
-
- meth << prereqs.map do |pre|
- pre = pre.to_s
- pre = file_task_name(pre) if Rake::Task[pre].is_a?(Rake::FileTask)
- ' ' + pre
- end.join("\n")
-
- meth << "\n\n" unless prereqs.empty? || actions.empty?
-
- meth << actions.map do |act|
- act = act.to_ruby
- unless act.gsub!(/^proc \{ \|(\w+)\|\n/,
- " \\1 = Struct.new(:name).new(#{name.inspect}) # A crude mock Rake::Task object\n")
- act.gsub!(/^proc \{\n/, '')
- end
- act.gsub(/\n\}$/, '')
- end.join("\n")
-
- meth << "\nend"
-
- if file_task
- @private_methods << meth
- return
- end
-
- meth
-end
-
-body = Rake::Task.tasks.map(&method(:method_for_task)).compact.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n")
-
-unless @private_methods.empty?
- body << "\n\n private\n\n"
- body << @private_methods.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n")
-end
-
-requires = $requires.map { |r| "require #{r.inspect}" }.join("\n")
-
-File.open(output, 'w') { |f| f.write(<:: name of the defaut task
- #
- def default_task(meth=nil)
- case meth
- when :none
- @default_task = 'help'
- when nil
- @default_task ||= from_superclass(:default_task, 'help')
- else
- @default_task = meth.to_s
- end
- end
-
- # Defines the usage and the description of the next task.
- #
- # ==== Parameters
- # usage
- # description
- #
- def desc(usage, description, options={})
- if options[:for]
- task = find_and_refresh_task(options[:for])
- task.usage = usage if usage
- task.description = description if description
- else
- @usage, @desc = usage, description
- end
- end
-
- # Maps an input to a task. If you define:
- #
- # map "-T" => "list"
- #
- # Running:
- #
- # thor -T
- #
- # Will invoke the list task.
- #
- # ==== Parameters
- # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task.
- #
- def map(mappings=nil)
- @map ||= from_superclass(:map, {})
-
- if mappings
- mappings.each do |key, value|
- if key.respond_to?(:each)
- key.each {|subkey| @map[subkey] = value}
- else
- @map[key] = value
- end
- end
- end
-
- @map
- end
-
- # Declares the options for the next task to be declared.
- #
- # ==== Parameters
- # Hash[Symbol => Object]:: The hash key is the name of the option and the value
- # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
- # or :required (string). If you give a value, the type of the value is used.
- #
- def method_options(options=nil)
- @method_options ||= {}
- build_options(options, @method_options) if options
- @method_options
- end
-
- # Adds an option to the set of class options. If :for is given as option,
- # it allows you to change the options from a previous defined task.
- #
- # def previous_task
- # # magic
- # end
- #
- # method_options :foo => :bar, :for => :previous_task
- #
- # def next_task
- # # magic
- # end
- #
- # ==== Parameters
- # name:: The name of the argument.
- # options:: Described below.
- #
- # ==== Options
- # :desc - Description for the argument.
- # :required - If the argument is required or not.
- # :default - Default value for this argument. It cannot be required and have default values.
- # :aliases - Aliases for this option.
- # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
- # :group - The group for this options. Use by class options to output options in different levels.
- # :banner - String to show on usage notes.
- #
- def method_option(name, options={})
- scope = if options[:for]
- find_and_refresh_task(options[:for]).options
- else
- method_options
- end
-
- build_option(name, options, scope)
- end
-
- # Parses the task and options from the given args, instantiate the class
- # and invoke the task. This method is used when the arguments must be parsed
- # from an array. If you are inside Ruby and want to use a Thor class, you
- # can simply initialize it:
- #
- # script = MyScript.new(args, options, config)
- # script.invoke(:task, first_arg, second_arg, third_arg)
- #
- def start(given_args=ARGV, config={})
- super do
- meth = normalize_task_name(given_args.shift)
- task = all_tasks[meth]
-
- if task
- args, opts = Thor::Options.split(given_args)
- config.merge!(:task_options => task.options)
- else
- args, opts = given_args, {}
- end
-
- task ||= Thor::Task::Dynamic.new(meth)
- trailing = args[Range.new(arguments.size, -1)]
- new(args, opts, config).invoke(task, trailing || [])
- end
- end
-
- # Prints help information. If a task name is given, it shows information
- # only about the specific task.
- #
- # ==== Parameters
- # meth:: An optional task name to print usage information about.
- #
- # ==== Options
- # namespace:: When true, shows the namespace in the output before the usage.
- # skip_inherited:: When true, does not show tasks from superclass.
- #
- def help(shell, meth=nil, options={})
- meth, options = nil, meth if meth.is_a?(Hash)
-
- if meth
- task = all_tasks[meth]
- raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task
-
- shell.say "Usage:"
- shell.say " #{banner(task, options[:namespace], false)}"
- shell.say
- class_options_help(shell, "Class", :Method => task.options.map { |_, o| o })
- shell.say task.description
- else
- list = (options[:short] ? tasks : all_tasks).map do |_, task|
- item = [ banner(task, options[:namespace]) ]
- item << "# #{task.short_description}" if task.short_description
- item << " "
- end
-
- options[:ident] ||= 2
- if options[:short]
- shell.print_list(list, :ident => options[:ident])
- else
- shell.say "Tasks:"
- shell.print_list(list, :ident => options[:ident])
- end
-
- Thor::Util.thor_classes_in(self).each do |subclass|
- namespace = options[:namespace] == true || subclass.namespace.gsub(/^#{self.namespace}:/, '')
- subclass.help(shell, options.merge(:short => true, :namespace => namespace))
- end
-
- class_options_help(shell, "Class") unless options[:short]
- end
- end
-
- protected
-
- # The banner for this class. You can customize it if you are invoking the
- # thor class by another ways which is not the Thor::Runner. It receives
- # the task that is going to be invoked and a boolean which indicates if
- # the namespace should be displayed as arguments.
- #
- def banner(task, namespace=true, show_options=true)
- task.formatted_usage(self, namespace, show_options)
- end
-
- def baseclass #:nodoc:
- Thor
- end
-
- def create_task(meth) #:nodoc:
- if @usage && @desc
- tasks[meth.to_s] = Thor::Task.new(meth, @desc, @usage, method_options)
- @usage, @desc, @method_options = nil
- true
- elsif self.all_tasks[meth.to_s] || meth.to_sym == :method_missing
- true
- else
- puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
- "Call desc if you want this method to be available as task or declare it inside a " <<
- "no_tasks{} block. Invoked from #{caller[1].inspect}."
- false
- end
- end
-
- def initialize_added #:nodoc:
- class_options.merge!(method_options)
- @method_options = nil
- end
-
- # Receives a task name (can be nil), and try to get a map from it.
- # If a map can't be found use the sent name or the default task.
- #
- def normalize_task_name(meth) #:nodoc:
- mapping = map[meth.to_s]
- meth = mapping || meth || default_task
- meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
- end
- end
-
- include Thor::Base
-
- map HELP_MAPPINGS => :help
-
- desc "help [TASK]", "Describe available tasks or one specific task"
- def help(task=nil)
- self.class.help(shell, task, :namespace => task && task.include?(?:))
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions.rb
deleted file mode 100644
index d561ccb2aa..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions.rb
+++ /dev/null
@@ -1,273 +0,0 @@
-require 'fileutils'
-
-Dir[File.join(File.dirname(__FILE__), "actions", "*.rb")].each do |action|
- require action
-end
-
-class Thor
- module Actions
- attr_accessor :behavior
-
- def self.included(base) #:nodoc:
- base.extend ClassMethods
- end
-
- module ClassMethods
- # Hold source paths for one Thor instance. source_paths_for_search is the
- # method responsible to gather source_paths from this current class,
- # inherited paths and the source root.
- #
- def source_paths
- @source_paths ||= []
- end
-
- # Returns the source paths in the following order:
- #
- # 1) This class source paths
- # 2) Source root
- # 3) Parents source paths
- #
- def source_paths_for_search
- paths = []
- paths += self.source_paths
- paths << self.source_root if self.respond_to?(:source_root)
- paths += from_superclass(:source_paths, [])
- paths
- end
-
- # Add runtime options that help actions execution.
- #
- def add_runtime_options!
- class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
- :desc => "Run but do not make any changes"
-
- class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
- :desc => "Overwrite files that already exist"
-
- class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
- :desc => "Skip files that already exist"
-
- class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime,
- :desc => "Supress status output"
- end
- end
-
- # Extends initializer to add more configuration options.
- #
- # ==== Configuration
- # behavior:: The actions default behavior. Can be :invoke or :revoke.
- # It also accepts :force, :skip and :pretend to set the behavior
- # and the respective option.
- #
- # destination_root:: The root directory needed for some actions.
- #
- def initialize(args=[], options={}, config={})
- self.behavior = case config[:behavior].to_s
- when "force", "skip"
- _cleanup_options_and_set(options, config[:behavior])
- :invoke
- when "revoke"
- :revoke
- else
- :invoke
- end
-
- super
- self.destination_root = config[:destination_root]
- end
-
- # Wraps an action object and call it accordingly to the thor class behavior.
- #
- def action(instance) #:nodoc:
- if behavior == :revoke
- instance.revoke!
- else
- instance.invoke!
- end
- end
-
- # Returns the root for this thor class (also aliased as destination root).
- #
- def destination_root
- @destination_stack.last
- end
-
- # Sets the root for this thor class. Relatives path are added to the
- # directory where the script was invoked and expanded.
- #
- def destination_root=(root)
- @destination_stack ||= []
- @destination_stack[0] = File.expand_path(root || '')
- end
-
- # Returns the given path relative to the absolute root (ie, root where
- # the script started).
- #
- def relative_to_original_destination_root(path, remove_dot=true)
- path = path.gsub(@destination_stack[0], '.')
- remove_dot ? (path[2..-1] || '') : path
- end
-
- # Holds source paths in instance so they can be manipulated.
- #
- def source_paths
- @source_paths ||= self.class.source_paths_for_search
- end
-
- # Receives a file or directory and search for it in the source paths.
- #
- def find_in_source_paths(file)
- relative_root = relative_to_original_destination_root(destination_root, false)
-
- source_paths.each do |source|
- source_file = File.expand_path(file, File.join(source, relative_root))
- return source_file if File.exists?(source_file)
- end
-
- if source_paths.empty?
- raise Error, "You don't have any source path defined for class #{self.class.name}. To fix this, " <<
- "you can define a source_root in your class."
- else
- raise Error, "Could not find #{file.inspect} in source paths."
- end
- end
-
- # Do something in the root or on a provided subfolder. If a relative path
- # is given it's referenced from the current root. The full path is yielded
- # to the block you provide. The path is set back to the previous path when
- # the method exits.
- #
- # ==== Parameters
- # dir:: the directory to move to.
- # config:: give :verbose => true to log and use padding.
- #
- def inside(dir='', config={}, &block)
- verbose = config.fetch(:verbose, false)
-
- say_status :inside, dir, verbose
- shell.padding += 1 if verbose
- @destination_stack.push File.expand_path(dir, destination_root)
-
- FileUtils.mkdir_p(destination_root) unless File.exist?(destination_root)
- FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
-
- @destination_stack.pop
- shell.padding -= 1 if verbose
- end
-
- # Goes to the root and execute the given block.
- #
- def in_root
- inside(@destination_stack.first) { yield }
- end
-
- # Loads an external file and execute it in the instance binding.
- #
- # ==== Parameters
- # path:: The path to the file to execute. Can be a web address or
- # a relative path from the source root.
- #
- # ==== Examples
- #
- # apply "http://gist.github.com/103208"
- #
- # apply "recipes/jquery.rb"
- #
- def apply(path, config={})
- verbose = config.fetch(:verbose, true)
- path = find_in_source_paths(path) unless path =~ /^http\:\/\//
-
- say_status :apply, path, verbose
- shell.padding += 1 if verbose
- instance_eval(open(path).read)
- shell.padding -= 1 if verbose
- end
-
- # Executes a command.
- #
- # ==== Parameters
- # command:: the command to be executed.
- # config:: give :verbose => false to not log the status. Specify :with
- # to append an executable to command executation.
- #
- # ==== Example
- #
- # inside('vendor') do
- # run('ln -s ~/edge rails')
- # end
- #
- def run(command, config={})
- return unless behavior == :invoke
-
- destination = relative_to_original_destination_root(destination_root, false)
- desc = "#{command} from #{destination.inspect}"
-
- if config[:with]
- desc = "#{File.basename(config[:with].to_s)} #{desc}"
- command = "#{config[:with]} #{command}"
- end
-
- say_status :run, desc, config.fetch(:verbose, true)
- system(command) unless options[:pretend]
- end
-
- # Executes a ruby script (taking into account WIN32 platform quirks).
- #
- # ==== Parameters
- # command:: the command to be executed.
- # config:: give :verbose => false to not log the status.
- #
- def run_ruby_script(command, config={})
- return unless behavior == :invoke
- run "#{command}", config.merge(:with => Thor::Util.ruby_command)
- end
-
- # Run a thor command. A hash of options can be given and it's converted to
- # switches.
- #
- # ==== Parameters
- # task:: the task to be invoked
- # args:: arguments to the task
- # config:: give :verbose => false to not log the status. Other options
- # are given as parameter to Thor.
- #
- # ==== Examples
- #
- # thor :install, "http://gist.github.com/103208"
- # #=> thor install http://gist.github.com/103208
- #
- # thor :list, :all => true, :substring => 'rails'
- # #=> thor list --all --substring=rails
- #
- def thor(task, *args)
- config = args.last.is_a?(Hash) ? args.pop : {}
- verbose = config.key?(:verbose) ? config.delete(:verbose) : true
-
- args.unshift task
- args.push Thor::Options.to_switches(config)
- command = args.join(' ').strip
-
- run command, :with => :thor, :verbose => verbose
- end
-
- protected
-
- # Allow current root to be shared between invocations.
- #
- def _shared_configuration #:nodoc:
- super.merge!(:destination_root => self.destination_root)
- end
-
- def _cleanup_options_and_set(options, key) #:nodoc:
- case options
- when Array
- %w(--force -f --skip -s).each { |i| options.delete(i) }
- options << "--#{key}"
- when Hash
- [:force, :skip, "force", "skip"].each { |i| options.delete(i) }
- options.merge!(key => true)
- end
- end
-
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/create_file.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/create_file.rb
deleted file mode 100644
index 8f6badee27..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/create_file.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-require 'thor/actions/empty_directory'
-
-class Thor
- module Actions
-
- # Create a new file relative to the destination root with the given data,
- # which is the return value of a block or a data string.
- #
- # ==== Parameters
- # destination:: the relative path to the destination root.
- # data:: the data to append to the file.
- # config:: give :verbose => false to not log the status.
- #
- # ==== Examples
- #
- # create_file "lib/fun_party.rb" do
- # hostname = ask("What is the virtual hostname I should use?")
- # "vhost.name = #{hostname}"
- # end
- #
- # create_file "config/apach.conf", "your apache config"
- #
- def create_file(destination, data=nil, config={}, &block)
- action CreateFile.new(self, destination, block || data.to_s, config)
- end
- alias :add_file :create_file
-
- # AddFile is a subset of Template, which instead of rendering a file with
- # ERB, it gets the content from the user.
- #
- class CreateFile < EmptyDirectory #:nodoc:
- attr_reader :data
-
- def initialize(base, destination, data, config={})
- @data = data
- super(base, destination, config)
- end
-
- # Checks if the content of the file at the destination is identical to the rendered result.
- #
- # ==== Returns
- # Boolean:: true if it is identical, false otherwise.
- #
- def identical?
- exists? && File.read(destination) == render
- end
-
- # Holds the content to be added to the file.
- #
- def render
- @render ||= if data.is_a?(Proc)
- data.call
- else
- data
- end
- end
-
- def invoke!
- invoke_with_conflict_check do
- FileUtils.mkdir_p(File.dirname(destination))
- File.open(destination, 'w'){ |f| f.write render }
- end
- end
-
- protected
-
- # Now on conflict we check if the file is identical or not.
- #
- def on_conflict_behavior(&block)
- if identical?
- say_status :identical, :blue
- else
- options = base.options.merge(config)
- force_or_skip_or_conflict(options[:force], options[:skip], &block)
- end
- end
-
- # If force is true, run the action, otherwise check if it's not being
- # skipped. If both are false, show the file_collision menu, if the menu
- # returns true, force it, otherwise skip.
- #
- def force_or_skip_or_conflict(force, skip, &block)
- if force
- say_status :force, :yellow
- block.call unless pretend?
- elsif skip
- say_status :skip, :yellow
- else
- say_status :conflict, :red
- force_or_skip_or_conflict(force_on_collision?, true, &block)
- end
- end
-
- # Shows the file collision menu to the user and gets the result.
- #
- def force_on_collision?
- base.shell.file_collision(destination){ render }
- end
-
- end
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/directory.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/directory.rb
deleted file mode 100644
index be5eb822ac..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/directory.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-require 'thor/actions/empty_directory'
-
-class Thor
- module Actions
-
- # Copies recursively the files from source directory to root directory.
- # If any of the files finishes with .tt, it's considered to be a template
- # and is placed in the destination without the extension .tt. If any
- # empty directory is found, it's copied and all .empty_directory files are
- # ignored. Remember that file paths can also be encoded, let's suppose a doc
- # directory with the following files:
- #
- # doc/
- # components/.empty_directory
- # README
- # rdoc.rb.tt
- # %app_name%.rb
- #
- # When invoked as:
- #
- # directory "doc"
- #
- # It will create a doc directory in the destination with the following
- # files (assuming that the app_name is "blog"):
- #
- # doc/
- # components/
- # README
- # rdoc.rb
- # blog.rb
- #
- # ==== Parameters
- # source:: the relative path to the source root.
- # destination:: the relative path to the destination root.
- # config:: give :verbose => false to not log the status.
- # If :recursive => false, does not look for paths recursively.
- #
- # ==== Examples
- #
- # directory "doc"
- # directory "doc", "docs", :recursive => false
- #
- def directory(source, destination=nil, config={})
- action Directory.new(self, source, destination || source, config)
- end
-
- class Directory < EmptyDirectory #:nodoc:
- attr_reader :source
-
- def initialize(base, source, destination=nil, config={})
- @source = File.expand_path(base.find_in_source_paths(source.to_s))
- super(base, destination, { :recursive => true }.merge(config))
- end
-
- def invoke!
- base.empty_directory given_destination, config
- execute!
- end
-
- def revoke!
- execute!
- end
-
- protected
-
- def execute!
- lookup = config[:recursive] ? File.join(source, '**') : source
- lookup = File.join(lookup, '{*,.[a-z]*}')
-
- Dir[lookup].each do |file_source|
- next if File.directory?(file_source)
- file_destination = File.join(given_destination, file_source.gsub(source, '.'))
-
- case file_source
- when /\.empty_directory$/
- base.empty_directory(File.dirname(file_destination), config)
- when /\.tt$/
- base.template(file_source, file_destination[0..-4], config)
- else
- base.copy_file(file_source, file_destination, config)
- end
- end
- end
-
- end
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/empty_directory.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/empty_directory.rb
deleted file mode 100644
index 03c1fe4af1..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/empty_directory.rb
+++ /dev/null
@@ -1,133 +0,0 @@
-class Thor
- module Actions
-
- # Creates an empty directory.
- #
- # ==== Parameters
- # destination:: the relative path to the destination root.
- # config:: give :verbose => false to not log the status.
- #
- # ==== Examples
- #
- # empty_directory "doc"
- #
- def empty_directory(destination, config={})
- action EmptyDirectory.new(self, destination, config)
- end
-
- # Class which holds create directory logic. This is the base class for
- # other actions like create_file and directory.
- #
- # This implementation is based in Templater actions, created by Jonas Nicklas
- # and Michael S. Klishin under MIT LICENSE.
- #
- class EmptyDirectory #:nodoc:
- attr_reader :base, :destination, :given_destination, :relative_destination, :config
-
- # Initializes given the source and destination.
- #
- # ==== Parameters
- # base:: A Thor::Base instance
- # source:: Relative path to the source of this file
- # destination:: Relative path to the destination of this file
- # config:: give :verbose => false to not log the status.
- #
- def initialize(base, destination, config={})
- @base, @config = base, { :verbose => true }.merge(config)
- self.destination = destination
- end
-
- # Checks if the destination file already exists.
- #
- # ==== Returns
- # Boolean:: true if the file exists, false otherwise.
- #
- def exists?
- ::File.exists?(destination)
- end
-
- def invoke!
- invoke_with_conflict_check do
- ::FileUtils.mkdir_p(destination)
- end
- end
-
- def revoke!
- say_status :remove, :red
- ::FileUtils.rm_rf(destination) if !pretend? && exists?
- end
-
- protected
-
- # Shortcut for pretend.
- #
- def pretend?
- base.options[:pretend]
- end
-
- # Sets the absolute destination value from a relative destination value.
- # It also stores the given and relative destination. Let's suppose our
- # script is being executed on "dest", it sets the destination root to
- # "dest". The destination, given_destination and relative_destination
- # are related in the following way:
- #
- # inside "bar" do
- # empty_directory "baz"
- # end
- #
- # destination #=> dest/bar/baz
- # relative_destination #=> bar/baz
- # given_destination #=> baz
- #
- def destination=(destination)
- if destination
- @given_destination = convert_encoded_instructions(destination.to_s)
- @destination = ::File.expand_path(@given_destination, base.destination_root)
- @relative_destination = base.relative_to_original_destination_root(@destination)
- end
- end
-
- # Filenames in the encoded form are converted. If you have a file:
- #
- # %class_name%.rb
- #
- # It gets the class name from the base and replace it:
- #
- # user.rb
- #
- def convert_encoded_instructions(filename)
- filename.gsub(/%(.*?)%/) do |string|
- instruction = $1.strip
- base.respond_to?(instruction) ? base.send(instruction) : string
- end
- end
-
- # Receives a hash of options and just execute the block if some
- # conditions are met.
- #
- def invoke_with_conflict_check(&block)
- if exists?
- on_conflict_behavior(&block)
- else
- say_status :create, :green
- block.call unless pretend?
- end
-
- destination
- end
-
- # What to do when the destination file already exists.
- #
- def on_conflict_behavior(&block)
- say_status :exist, :blue
- end
-
- # Shortcut to say_status shell method.
- #
- def say_status(status, color)
- base.shell.say_status status, relative_destination, color if config[:verbose]
- end
-
- end
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/file_manipulation.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/file_manipulation.rb
deleted file mode 100644
index d77d90d448..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/file_manipulation.rb
+++ /dev/null
@@ -1,219 +0,0 @@
-require 'erb'
-require 'open-uri'
-
-class Thor
- module Actions
-
- # Copies the file from the relative source to the relative destination. If
- # the destination is not given it's assumed to be equal to the source.
- #
- # ==== Parameters
- # source:: the relative path to the source root.
- # destination:: the relative path to the destination root.
- # config:: give :verbose => false to not log the status.
- #
- # ==== Examples
- #
- # copy_file "README", "doc/README"
- #
- # copy_file "doc/README"
- #
- def copy_file(source, destination=nil, config={})
- destination ||= source
- source = File.expand_path(find_in_source_paths(source.to_s))
-
- create_file destination, nil, config do
- File.read(source)
- end
- end
-
- # Gets the content at the given address and places it at the given relative
- # destination. If a block is given instead of destination, the content of
- # the url is yielded and used as location.
- #
- # ==== Parameters
- # source:: the address of the given content.
- # destination:: the relative path to the destination root.
- # config:: give :verbose => false to not log the status.
- #
- # ==== Examples
- #
- # get "http://gist.github.com/103208", "doc/README"
- #
- # get "http://gist.github.com/103208" do |content|
- # content.split("\n").first
- # end
- #
- def get(source, destination=nil, config={}, &block)
- source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^http\:\/\//
- render = open(source).read
-
- destination ||= if block_given?
- block.arity == 1 ? block.call(render) : block.call
- else
- File.basename(source)
- end
-
- create_file destination, render, config
- end
-
- # Gets an ERB template at the relative source, executes it and makes a copy
- # at the relative destination. If the destination is not given it's assumed
- # to be equal to the source removing .tt from the filename.
- #
- # ==== Parameters
- # source:: the relative path to the source root.
- # destination:: the relative path to the destination root.
- # config:: give :verbose => false to not log the status.
- #
- # ==== Examples
- #
- # template "README", "doc/README"
- #
- # template "doc/README"
- #
- def template(source, destination=nil, config={})
- destination ||= source
- source = File.expand_path(find_in_source_paths(source.to_s))
- context = instance_eval('binding')
-
- create_file destination, nil, config do
- ERB.new(::File.read(source), nil, '-').result(context)
- end
- end
-
- # Changes the mode of the given file or directory.
- #
- # ==== Parameters
- # mode:: the file mode
- # path:: the name of the file to change mode
- # config:: give :verbose => false to not log the status.
- #
- # ==== Example
- #
- # chmod "script/*", 0755
- #
- def chmod(path, mode, config={})
- return unless behavior == :invoke
- path = File.expand_path(path, destination_root)
- say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
- FileUtils.chmod_R(mode, path) unless options[:pretend]
- end
-
- # Prepend text to a file. Since it depends on inject_into_file, it's reversible.
- #
- # ==== Parameters
- # path:: path of the file to be changed
- # data:: the data to prepend to the file, can be also given as a block.
- # config:: give :verbose => false to not log the status.
- #
- # ==== Example
- #
- # prepend_file 'config/environments/test.rb', 'config.gem "rspec"'
- #
- # prepend_file 'config/environments/test.rb' do
- # 'config.gem "rspec"'
- # end
- #
- def prepend_file(path, *args, &block)
- config = args.last.is_a?(Hash) ? args.pop : {}
- config.merge!(:after => /\A/)
- inject_into_file(path, *(args << config), &block)
- end
-
- # Append text to a file. Since it depends on inject_into_file, it's reversible.
- #
- # ==== Parameters
- # path:: path of the file to be changed
- # data:: the data to append to the file, can be also given as a block.
- # config:: give :verbose => false to not log the status.
- #
- # ==== Example
- #
- # append_file 'config/environments/test.rb', 'config.gem "rspec"'
- #
- # append_file 'config/environments/test.rb' do
- # 'config.gem "rspec"'
- # end
- #
- def append_file(path, *args, &block)
- config = args.last.is_a?(Hash) ? args.pop : {}
- config.merge!(:before => /\z/)
- inject_into_file(path, *(args << config), &block)
- end
-
- # Injects text right after the class definition. Since it depends on
- # inject_into_file, it's reversible.
- #
- # ==== Parameters
- # path:: path of the file to be changed
- # klass:: the class to be manipulated
- # data:: the data to append to the class, can be also given as a block.
- # config:: give :verbose => false to not log the status.
- #
- # ==== Examples
- #
- # inject_into_class "app/controllers/application_controller.rb", " filter_parameter :password\n"
- #
- # inject_into_class "app/controllers/application_controller.rb", ApplicationController do
- # " filter_parameter :password\n"
- # end
- #
- def inject_into_class(path, klass, *args, &block)
- config = args.last.is_a?(Hash) ? args.pop : {}
- config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/)
- inject_into_file(path, *(args << config), &block)
- end
-
- # Run a regular expression replacement on a file.
- #
- # ==== Parameters
- # path:: path of the file to be changed
- # flag:: the regexp or string to be replaced
- # replacement:: the replacement, can be also given as a block
- # config:: give :verbose => false to not log the status.
- #
- # ==== Example
- #
- # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
- #
- # gsub_file 'README', /rake/, :green do |match|
- # match << " no more. Use thor!"
- # end
- #
- def gsub_file(path, flag, *args, &block)
- return unless behavior == :invoke
- config = args.last.is_a?(Hash) ? args.pop : {}
-
- path = File.expand_path(path, destination_root)
- say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
-
- unless options[:pretend]
- content = File.read(path)
- content.gsub!(flag, *args, &block)
- File.open(path, 'wb') { |file| file.write(content) }
- end
- end
-
- # Removes a file at the given location.
- #
- # ==== Parameters
- # path:: path of the file to be changed
- # config:: give :verbose => false to not log the status.
- #
- # ==== Example
- #
- # remove_file 'README'
- # remove_file 'app/controllers/application_controller.rb'
- #
- def remove_file(path, config={})
- return unless behavior == :invoke
- path = File.expand_path(path, destination_root)
-
- say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
- ::FileUtils.rm_rf(path) if !options[:pretend] && File.exists?(path)
- end
- alias :remove_dir :remove_file
-
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/inject_into_file.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/inject_into_file.rb
deleted file mode 100644
index 0636ec6591..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/inject_into_file.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-require 'thor/actions/empty_directory'
-
-class Thor
- module Actions
-
- # Injects the given content into a file. Different from gsub_file, this
- # method is reversible.
- #
- # ==== Parameters
- # destination:: Relative path to the destination root
- # data:: Data to add to the file. Can be given as a block.
- # config:: give :verbose => false to not log the status and the flag
- # for injection (:after or :before).
- #
- # ==== Examples
- #
- # inject_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
- #
- # inject_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
- # gems = ask "Which gems would you like to add?"
- # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
- # end
- #
- def inject_into_file(destination, *args, &block)
- if block_given?
- data, config = block, args.shift
- else
- data, config = args.shift, args.shift
- end
- action InjectIntoFile.new(self, destination, data, config)
- end
-
- class InjectIntoFile < EmptyDirectory #:nodoc:
- attr_reader :replacement, :flag, :behavior
-
- def initialize(base, destination, data, config)
- super(base, destination, { :verbose => true }.merge(config))
-
- @behavior, @flag = if @config.key?(:after)
- [:after, @config.delete(:after)]
- else
- [:before, @config.delete(:before)]
- end
-
- @replacement = data.is_a?(Proc) ? data.call : data
- @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
- end
-
- def invoke!
- say_status :invoke
-
- content = if @behavior == :after
- '\0' + replacement
- else
- replacement + '\0'
- end
-
- replace!(/#{flag}/, content)
- end
-
- def revoke!
- say_status :revoke
-
- regexp = if @behavior == :after
- content = '\1\2'
- /(#{flag})(.*)(#{Regexp.escape(replacement)})/m
- else
- content = '\2\3'
- /(#{Regexp.escape(replacement)})(.*)(#{flag})/m
- end
-
- replace!(regexp, content)
- end
-
- protected
-
- def say_status(behavior)
- status = if flag == /\A/
- behavior == :invoke ? :prepend : :unprepend
- elsif flag == /\z/
- behavior == :invoke ? :append : :unappend
- else
- behavior == :invoke ? :inject : :deinject
- end
-
- super(status, config[:verbose])
- end
-
- # Adds the content to the file.
- #
- def replace!(regexp, string)
- unless base.options[:pretend]
- content = File.read(destination)
- content.gsub!(regexp, string)
- File.open(destination, 'wb') { |file| file.write(content) }
- end
- end
-
- end
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/base.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/base.rb
deleted file mode 100644
index 700d794123..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/base.rb
+++ /dev/null
@@ -1,517 +0,0 @@
-require 'thor/core_ext/hash_with_indifferent_access'
-require 'thor/core_ext/ordered_hash'
-require 'thor/error'
-require 'thor/shell'
-require 'thor/invocation'
-require 'thor/parser'
-require 'thor/task'
-require 'thor/util'
-
-class Thor
- # Shortcuts for help.
- HELP_MAPPINGS = %w(-h -? --help -D)
-
- # Thor methods that should not be overwritten by the user.
- THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root
- action add_file create_file in_root inside run run_ruby_script)
-
- module Base
- attr_accessor :options
-
- # It receives arguments in an Array and two hashes, one for options and
- # other for configuration.
- #
- # Notice that it does not check if all required arguments were supplied.
- # It should be done by the parser.
- #
- # ==== Parameters
- # args:: An array of objects. The objects are applied to their
- # respective accessors declared with argument.
- #
- # options:: An options hash that will be available as self.options.
- # The hash given is converted to a hash with indifferent
- # access, magic predicates (options.skip?) and then frozen.
- #
- # config:: Configuration for this Thor class.
- #
- def initialize(args=[], options={}, config={})
- Thor::Arguments.parse(self.class.arguments, args).each do |key, value|
- send("#{key}=", value)
- end
-
- parse_options = self.class.class_options
-
- if options.is_a?(Array)
- task_options = config.delete(:task_options) # hook for start
- parse_options = parse_options.merge(task_options) if task_options
- array_options, hash_options = options, {}
- else
- array_options, hash_options = [], options
- end
-
- options = Thor::Options.parse(parse_options, array_options)
- self.options = Thor::CoreExt::HashWithIndifferentAccess.new(options).merge!(hash_options)
- self.options.freeze
- end
-
- class << self
- def included(base) #:nodoc:
- base.send :extend, ClassMethods
- base.send :include, Invocation
- base.send :include, Shell
- end
-
- # Returns the classes that inherits from Thor or Thor::Group.
- #
- # ==== Returns
- # Array[Class]
- #
- def subclasses
- @subclasses ||= []
- end
-
- # Returns the files where the subclasses are kept.
- #
- # ==== Returns
- # Hash[path => Class]
- #
- def subclass_files
- @subclass_files ||= Hash.new{ |h,k| h[k] = [] }
- end
-
- # Whenever a class inherits from Thor or Thor::Group, we should track the
- # class and the file on Thor::Base. This is the method responsable for it.
- #
- def register_klass_file(klass) #:nodoc:
- file = caller[1].match(/(.*):\d+/)[1]
- Thor::Base.subclasses << klass unless Thor::Base.subclasses.include?(klass)
-
- file_subclasses = Thor::Base.subclass_files[File.expand_path(file)]
- file_subclasses << klass unless file_subclasses.include?(klass)
- end
- end
-
- module ClassMethods
- # Adds an argument to the class and creates an attr_accessor for it.
- #
- # Arguments are different from options in several aspects. The first one
- # is how they are parsed from the command line, arguments are retrieved
- # from position:
- #
- # thor task NAME
- #
- # Instead of:
- #
- # thor task --name=NAME
- #
- # Besides, arguments are used inside your code as an accessor (self.argument),
- # while options are all kept in a hash (self.options).
- #
- # Finally, arguments cannot have type :default or :boolean but can be
- # optional (supplying :optional => :true or :required => false), although
- # you cannot have a required argument after a non-required argument. If you
- # try it, an error is raised.
- #
- # ==== Parameters
- # name:: The name of the argument.
- # options:: Described below.
- #
- # ==== Options
- # :desc - Description for the argument.
- # :required - If the argument is required or not.
- # :optional - If the argument is optional or not.
- # :type - The type of the argument, can be :string, :hash, :array, :numeric.
- # :default - Default value for this argument. It cannot be required and have default values.
- # :banner - String to show on usage notes.
- #
- # ==== Errors
- # ArgumentError:: Raised if you supply a required argument after a non required one.
- #
- def argument(name, options={})
- is_thor_reserved_word?(name, :argument)
- no_tasks { attr_accessor name }
-
- required = if options.key?(:optional)
- !options[:optional]
- elsif options.key?(:required)
- options[:required]
- else
- options[:default].nil?
- end
-
- remove_argument name
-
- arguments.each do |argument|
- next if argument.required?
- raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " <<
- "the non-required argument #{argument.human_name.inspect}."
- end if required
-
- arguments << Thor::Argument.new(name, options[:desc], required, options[:type],
- options[:default], options[:banner])
- end
-
- # Returns this class arguments, looking up in the ancestors chain.
- #
- # ==== Returns
- # Array[Thor::Argument]
- #
- def arguments
- @arguments ||= from_superclass(:arguments, [])
- end
-
- # Adds a bunch of options to the set of class options.
- #
- # class_options :foo => false, :bar => :required, :baz => :string
- #
- # If you prefer more detailed declaration, check class_option.
- #
- # ==== Parameters
- # Hash[Symbol => Object]
- #
- def class_options(options=nil)
- @class_options ||= from_superclass(:class_options, {})
- build_options(options, @class_options) if options
- @class_options
- end
-
- # Adds an option to the set of class options
- #
- # ==== Parameters
- # name:: The name of the argument.
- # options:: Described below.
- #
- # ==== Options
- # :desc - Description for the argument.
- # :required - If the argument is required or not.
- # :default - Default value for this argument.
- # :group - The group for this options. Use by class options to output options in different levels.
- # :aliases - Aliases for this option.
- # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
- # :banner - String to show on usage notes.
- #
- def class_option(name, options={})
- build_option(name, options, class_options)
- end
-
- # Removes a previous defined argument. If :undefine is given, undefine
- # accessors as well.
- #
- # ==== Paremeters
- # names:: Arguments to be removed
- #
- # ==== Examples
- #
- # remove_argument :foo
- # remove_argument :foo, :bar, :baz, :undefine => true
- #
- def remove_argument(*names)
- options = names.last.is_a?(Hash) ? names.pop : {}
-
- names.each do |name|
- arguments.delete_if { |a| a.name == name.to_s }
- undef_method name, "#{name}=" if options[:undefine]
- end
- end
-
- # Removes a previous defined class option.
- #
- # ==== Paremeters
- # names:: Class options to be removed
- #
- # ==== Examples
- #
- # remove_class_option :foo
- # remove_class_option :foo, :bar, :baz
- #
- def remove_class_option(*names)
- names.each do |name|
- class_options.delete(name)
- end
- end
-
- # Defines the group. This is used when thor list is invoked so you can specify
- # that only tasks from a pre-defined group will be shown. Defaults to standard.
- #
- # ==== Parameters
- # name
- #
- def group(name=nil)
- case name
- when nil
- @group ||= from_superclass(:group, 'standard')
- else
- @group = name.to_s
- end
- end
-
- # Returns the tasks for this Thor class.
- #
- # ==== Returns
- # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
- # objects as values.
- #
- def tasks
- @tasks ||= Thor::CoreExt::OrderedHash.new
- end
-
- # Returns the tasks for this Thor class and all subclasses.
- #
- # ==== Returns
- # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
- # objects as values.
- #
- def all_tasks
- @all_tasks ||= from_superclass(:all_tasks, Thor::CoreExt::OrderedHash.new)
- @all_tasks.merge(tasks)
- end
-
- # Removes a given task from this Thor class. This is usually done if you
- # are inheriting from another class and don't want it to be available
- # anymore.
- #
- # By default it only remove the mapping to the task. But you can supply
- # :undefine => true to undefine the method from the class as well.
- #
- # ==== Parameters
- # name:: The name of the task to be removed
- # options:: You can give :undefine => true if you want tasks the method
- # to be undefined from the class as well.
- #
- def remove_task(*names)
- options = names.last.is_a?(Hash) ? names.pop : {}
-
- names.each do |name|
- tasks.delete(name.to_s)
- all_tasks.delete(name.to_s)
- undef_method name if options[:undefine]
- end
- end
-
- # All methods defined inside the given block are not added as tasks.
- #
- # So you can do:
- #
- # class MyScript < Thor
- # no_tasks do
- # def this_is_not_a_task
- # end
- # end
- # end
- #
- # You can also add the method and remove it from the task list:
- #
- # class MyScript < Thor
- # def this_is_not_a_task
- # end
- # remove_task :this_is_not_a_task
- # end
- #
- def no_tasks
- @no_tasks = true
- yield
- @no_tasks = false
- end
-
- # Sets the namespace for the Thor or Thor::Group class. By default the
- # namespace is retrieved from the class name. If your Thor class is named
- # Scripts::MyScript, the help method, for example, will be called as:
- #
- # thor scripts:my_script -h
- #
- # If you change the namespace:
- #
- # namespace :my_scripts
- #
- # You change how your tasks are invoked:
- #
- # thor my_scripts -h
- #
- # Finally, if you change your namespace to default:
- #
- # namespace :default
- #
- # Your tasks can be invoked with a shortcut. Instead of:
- #
- # thor :my_task
- #
- def namespace(name=nil)
- case name
- when nil
- @namespace ||= Thor::Util.namespace_from_thor_class(self, false)
- else
- @namespace = name.to_s
- end
- end
-
- # Default way to start generators from the command line.
- #
- def start(given_args=ARGV, config={})
- config[:shell] ||= Thor::Base.shell.new
- yield
- rescue Thor::Error => e
- if given_args.include?("--debug")
- raise e
- else
- config[:shell].error e.message
- end
- exit(1) if exit_on_failure?
- end
-
- protected
-
- # Prints the class options per group. If an option does not belong to
- # any group, it uses the ungrouped name value. This method provide to
- # hooks to add extra options, one of them if the third argument called
- # extra_group that should be a hash in the format :group => Array[Options].
- #
- # The second is by returning a lambda used to print values. The lambda
- # requires two options: the group name and the array of options.
- #
- def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc:
- groups = {}
-
- class_options.each do |_, value|
- groups[value.group] ||= []
- groups[value.group] << value
- end
-
- printer = proc do |group_name, options|
- list = []
- padding = options.collect{ |o| o.aliases.size }.max.to_i * 4
-
- options.each do |option|
- item = [ option.usage(padding) ]
- item.push(option.description ? "# #{option.description}" : "")
-
- list << item
- list << [ "", "# Default: #{option.default}" ] if option.show_default?
- end
-
- unless list.empty?
- shell.say(group_name ? "#{group_name} options:" : "Options:")
- shell.print_table(list, :ident => 2)
- shell.say ""
- end
- end
-
- # Deal with default group
- global_options = groups.delete(nil) || []
- printer.call(ungrouped_name, global_options) if global_options
-
- # Print all others
- groups = extra_group.merge(groups) if extra_group
- groups.each(&printer)
- printer
- end
-
- # Raises an error if the word given is a Thor reserved word.
- #
- def is_thor_reserved_word?(word, type) #:nodoc:
- return false unless THOR_RESERVED_WORDS.include?(word.to_s)
- raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}"
- end
-
- # Build an option and adds it to the given scope.
- #
- # ==== Parameters
- # name:: The name of the argument.
- # options:: Described in both class_option and method_option.
- #
- def build_option(name, options, scope) #:nodoc:
- scope[name] = Thor::Option.new(name, options[:desc], options[:required],
- options[:type], options[:default], options[:banner],
- options[:group], options[:aliases])
- end
-
- # Receives a hash of options, parse them and add to the scope. This is a
- # fast way to set a bunch of options:
- #
- # build_options :foo => true, :bar => :required, :baz => :string
- #
- # ==== Parameters
- # Hash[Symbol => Object]
- #
- def build_options(options, scope) #:nodoc:
- options.each do |key, value|
- scope[key] = Thor::Option.parse(key, value)
- end
- end
-
- # Finds a task with the given name. If the task belongs to the current
- # class, just return it, otherwise dup it and add the fresh copy to the
- # current task hash.
- #
- def find_and_refresh_task(name) #:nodoc:
- task = if task = tasks[name.to_s]
- task
- elsif task = all_tasks[name.to_s]
- tasks[name.to_s] = task.clone
- else
- raise ArgumentError, "You supplied :for => #{name.inspect}, but the task #{name.inspect} could not be found."
- end
- end
-
- # Everytime someone inherits from a Thor class, register the klass
- # and file into baseclass.
- #
- def inherited(klass)
- Thor::Base.register_klass_file(klass)
- end
-
- # Fire this callback whenever a method is added. Added methods are
- # tracked as tasks by invoking the create_task method.
- #
- def method_added(meth)
- meth = meth.to_s
-
- if meth == "initialize"
- initialize_added
- return
- end
-
- # Return if it's not a public instance method
- return unless public_instance_methods.include?(meth) ||
- public_instance_methods.include?(meth.to_sym)
-
- return if @no_tasks || !create_task(meth)
-
- is_thor_reserved_word?(meth, :task)
- Thor::Base.register_klass_file(self)
- end
-
- # Retrieves a value from superclass. If it reaches the baseclass,
- # returns default.
- #
- def from_superclass(method, default=nil)
- if self == baseclass || !superclass.respond_to?(method, true)
- default
- else
- value = superclass.send(method)
- value.dup if value
- end
- end
-
- # A flag that makes the process exit with status 1 if any error happens.
- #
- def exit_on_failure?
- false
- end
-
- # SIGNATURE: Sets the baseclass. This is where the superclass lookup
- # finishes.
- def baseclass #:nodoc:
- end
-
- # SIGNATURE: Creates a new task if valid_task? is true. This method is
- # called when a new method is added to the class.
- def create_task(meth) #:nodoc:
- end
-
- # SIGNATURE: Defines behavior when the initialize method is added to the
- # class.
- def initialize_added #:nodoc:
- end
- end
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/hash_with_indifferent_access.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/hash_with_indifferent_access.rb
deleted file mode 100644
index 78bc5cf4bf..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/hash_with_indifferent_access.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-class Thor
- module CoreExt #:nodoc:
-
- # A hash with indifferent access and magic predicates.
- #
- # hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
- #
- # hash[:foo] #=> 'bar'
- # hash['foo'] #=> 'bar'
- # hash.foo? #=> true
- #
- class HashWithIndifferentAccess < ::Hash #:nodoc:
-
- def initialize(hash={})
- super()
- hash.each do |key, value|
- self[convert_key(key)] = value
- end
- end
-
- def [](key)
- super(convert_key(key))
- end
-
- def []=(key, value)
- super(convert_key(key), value)
- end
-
- def delete(key)
- super(convert_key(key))
- end
-
- def values_at(*indices)
- indices.collect { |key| self[convert_key(key)] }
- end
-
- def merge(other)
- dup.merge!(other)
- end
-
- def merge!(other)
- other.each do |key, value|
- self[convert_key(key)] = value
- end
- self
- end
-
- protected
-
- def convert_key(key)
- key.is_a?(Symbol) ? key.to_s : key
- end
-
- # Magic predicates. For instance:
- #
- # options.force? # => !!options['force']
- # options.shebang # => "/usr/lib/local/ruby"
- # options.test_framework?(:rspec) # => options[:test_framework] == :rspec
- #
- def method_missing(method, *args, &block)
- method = method.to_s
- if method =~ /^(\w+)\?$/
- if args.empty?
- !!self[$1]
- else
- self[$1] == args.first
- end
- else
- self[method]
- end
- end
-
- end
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/ordered_hash.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/ordered_hash.rb
deleted file mode 100644
index 27fea5bb35..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/ordered_hash.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-class Thor
- module CoreExt #:nodoc:
-
- if RUBY_VERSION >= '1.9'
- class OrderedHash < ::Hash
- end
- else
- # This class is based on the Ruby 1.9 ordered hashes.
- #
- # It keeps the semantics and most of the efficiency of normal hashes
- # while also keeping track of the order in which elements were set.
- #
- class OrderedHash #:nodoc:
- include Enumerable
-
- Node = Struct.new(:key, :value, :next, :prev)
-
- def initialize
- @hash = {}
- end
-
- def [](key)
- @hash[key] && @hash[key].value
- end
-
- def []=(key, value)
- if node = @hash[key]
- node.value = value
- else
- node = Node.new(key, value)
-
- if @first.nil?
- @first = @last = node
- else
- node.prev = @last
- @last.next = node
- @last = node
- end
- end
-
- @hash[key] = node
- value
- end
-
- def delete(key)
- if node = @hash[key]
- prev_node = node.prev
- next_node = node.next
-
- next_node.prev = prev_node if next_node
- prev_node.next = next_node if prev_node
-
- @first = next_node if @first == node
- @last = prev_node if @last == node
-
- value = node.value
- end
-
- @hash.delete(key)
- value
- end
-
- def keys
- self.map { |k, v| k }
- end
-
- def values
- self.map { |k, v| v }
- end
-
- def each
- return unless @first
- yield [@first.key, @first.value]
- node = @first
- yield [node.key, node.value] while node = node.next
- self
- end
-
- def merge(other)
- hash = self.class.new
-
- self.each do |key, value|
- hash[key] = value
- end
-
- other.each do |key, value|
- hash[key] = value
- end
-
- hash
- end
-
- def empty?
- @hash.empty?
- end
- end
- end
-
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/error.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/error.rb
deleted file mode 100644
index f9b31a35d1..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/error.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-class Thor
- # Thor::Error is raised when it's caused by wrong usage of thor classes. Those
- # errors have their backtrace supressed and are nicely shown to the user.
- #
- # Errors that are caused by the developer, like declaring a method which
- # overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we
- # ensure that developer errors are shown with full backtrace.
- #
- class Error < StandardError
- end
-
- # Raised when a task was not found.
- #
- class UndefinedTaskError < Error
- end
-
- # Raised when a task was found, but not invoked properly.
- #
- class InvocationError < Error
- end
-
- class RequiredArgumentMissingError < InvocationError
- end
-
- class MalformattedArgumentError < InvocationError
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/group.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/group.rb
deleted file mode 100644
index 1e59df2313..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/group.rb
+++ /dev/null
@@ -1,263 +0,0 @@
-# Thor has a special class called Thor::Group. The main difference to Thor class
-# is that it invokes all tasks at once. It also include some methods that allows
-# invocations to be done at the class method, which are not available to Thor
-# tasks.
-#
-class Thor::Group
- class << self
- # The descrition for this Thor::Group. If none is provided, but a source root
- # exists, tries to find the USAGE one folder above it, otherwise searches
- # in the superclass.
- #
- # ==== Parameters
- # description:: The description for this Thor::Group.
- #
- def desc(description=nil)
- case description
- when nil
- @desc ||= from_superclass(:desc, nil)
- else
- @desc = description
- end
- end
-
- # Start works differently in Thor::Group, it simply invokes all tasks
- # inside the class.
- #
- def start(given_args=ARGV, config={})
- super do
- if Thor::HELP_MAPPINGS.include?(given_args.first)
- help(config[:shell])
- return
- end
-
- args, opts = Thor::Options.split(given_args)
- new(args, opts, config).invoke
- end
- end
-
- # Prints help information.
- #
- # ==== Options
- # short:: When true, shows only usage.
- #
- def help(shell, options={})
- if options[:short]
- shell.say banner
- else
- shell.say "Usage:"
- shell.say " #{banner}"
- shell.say
- class_options_help(shell)
- shell.say self.desc if self.desc
- end
- end
-
- # Stores invocations for this class merging with superclass values.
- #
- def invocations #:nodoc:
- @invocations ||= from_superclass(:invocations, {})
- end
-
- # Stores invocation blocks used on invoke_from_option.
- #
- def invocation_blocks #:nodoc:
- @invocation_blocks ||= from_superclass(:invocation_blocks, {})
- end
-
- # Invoke the given namespace or class given. It adds an instance
- # method that will invoke the klass and task. You can give a block to
- # configure how it will be invoked.
- #
- # The namespace/class given will have its options showed on the help
- # usage. Check invoke_from_option for more information.
- #
- def invoke(*names, &block)
- options = names.last.is_a?(Hash) ? names.pop : {}
- verbose = options.fetch(:verbose, :white)
-
- names.each do |name|
- invocations[name] = false
- invocation_blocks[name] = block if block_given?
-
- class_eval <<-METHOD, __FILE__, __LINE__
- def _invoke_#{name.to_s.gsub(/\W/, '_')}
- klass, task = self.class.prepare_for_invocation(nil, #{name.inspect})
-
- if klass
- say_status :invoke, #{name.inspect}, #{verbose.inspect}
- block = self.class.invocation_blocks[#{name.inspect}]
- _invoke_for_class_method klass, task, &block
- else
- say_status :error, %(#{name.inspect} [not found]), :red
- end
- end
- METHOD
- end
- end
-
- # Invoke a thor class based on the value supplied by the user to the
- # given option named "name". A class option must be created before this
- # method is invoked for each name given.
- #
- # ==== Examples
- #
- # class GemGenerator < Thor::Group
- # class_option :test_framework, :type => :string
- # invoke_from_option :test_framework
- # end
- #
- # ==== Boolean options
- #
- # In some cases, you want to invoke a thor class if some option is true or
- # false. This is automatically handled by invoke_from_option. Then the
- # option name is used to invoke the generator.
- #
- # ==== Preparing for invocation
- #
- # In some cases you want to customize how a specified hook is going to be
- # invoked. You can do that by overwriting the class method
- # prepare_for_invocation. The class method must necessarily return a klass
- # and an optional task.
- #
- # ==== Custom invocations
- #
- # You can also supply a block to customize how the option is giong to be
- # invoked. The block receives two parameters, an instance of the current
- # class and the klass to be invoked.
- #
- def invoke_from_option(*names, &block)
- options = names.last.is_a?(Hash) ? names.pop : {}
- verbose = options.fetch(:verbose, :white)
-
- names.each do |name|
- unless class_options.key?(name)
- raise ArgumentError, "You have to define the option #{name.inspect} " <<
- "before setting invoke_from_option."
- end
-
- invocations[name] = true
- invocation_blocks[name] = block if block_given?
-
- class_eval <<-METHOD, __FILE__, __LINE__
- def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')}
- return unless options[#{name.inspect}]
-
- value = options[#{name.inspect}]
- value = #{name.inspect} if TrueClass === value
- klass, task = self.class.prepare_for_invocation(#{name.inspect}, value)
-
- if klass
- say_status :invoke, value, #{verbose.inspect}
- block = self.class.invocation_blocks[#{name.inspect}]
- _invoke_for_class_method klass, task, &block
- else
- say_status :error, %(\#{value} [not found]), :red
- end
- end
- METHOD
- end
- end
-
- # Remove a previously added invocation.
- #
- # ==== Examples
- #
- # remove_invocation :test_framework
- #
- def remove_invocation(*names)
- names.each do |name|
- remove_task(name)
- remove_class_option(name)
- invocations.delete(name)
- invocation_blocks.delete(name)
- end
- end
-
- # Overwrite class options help to allow invoked generators options to be
- # shown recursively when invoking a generator.
- #
- def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc:
- group_options = {}
-
- get_options_from_invocations(group_options, class_options) do |klass|
- klass.send(:get_options_from_invocations, group_options, class_options)
- end
-
- group_options.merge!(extra_group) if extra_group
- super(shell, ungrouped_name, group_options)
- end
-
- # Get invocations array and merge options from invocations. Those
- # options are added to group_options hash. Options that already exists
- # in base_options are not added twice.
- #
- def get_options_from_invocations(group_options, base_options) #:nodoc:
- invocations.each do |name, from_option|
- value = if from_option
- option = class_options[name]
- option.type == :boolean ? name : option.default
- else
- name
- end
- next unless value
-
- klass, task = prepare_for_invocation(name, value)
- next unless klass && klass.respond_to?(:class_options)
-
- value = value.to_s
- human_name = value.respond_to?(:classify) ? value.classify : value
-
- group_options[human_name] ||= []
- group_options[human_name] += klass.class_options.values.select do |option|
- base_options[option.name.to_sym].nil? && option.group.nil? &&
- !group_options.values.flatten.any? { |i| i.name == option.name }
- end
-
- yield klass if block_given?
- end
- end
-
- protected
-
- # The banner for this class. You can customize it if you are invoking the
- # thor class by another ways which is not the Thor::Runner.
- #
- def banner
- "#{self.namespace} #{self.arguments.map {|a| a.usage }.join(' ')}"
- end
-
- def baseclass #:nodoc:
- Thor::Group
- end
-
- def create_task(meth) #:nodoc:
- tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil)
- true
- end
- end
-
- include Thor::Base
-
- protected
-
- # Shortcut to invoke with padding and block handling. Use internally by
- # invoke and invoke_from_option class methods.
- #
- def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc:
- shell.padding += 1
-
- result = if block_given?
- if block.arity == 2
- block.call(self, klass)
- else
- block.call(self, klass, task)
- end
- else
- invoke klass, task, *args
- end
-
- shell.padding -= 1
- result
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/invocation.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/invocation.rb
deleted file mode 100644
index 32e6a72454..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/invocation.rb
+++ /dev/null
@@ -1,178 +0,0 @@
-class Thor
- module Invocation
- def self.included(base) #:nodoc:
- base.extend ClassMethods
- end
-
- module ClassMethods
- # Prepare for class methods invocations. This method must return a klass to
- # have the invoked class options showed in help messages in generators.
- #
- def prepare_for_invocation(key, name) #:nodoc:
- case name
- when Symbol, String
- Thor::Util.namespace_to_thor_class_and_task(name.to_s, false)
- else
- name
- end
- end
- end
-
- # Make initializer aware of invocations and the initializer proc.
- #
- def initialize(args=[], options={}, config={}, &block) #:nodoc:
- @_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
- @_initializer = [ args, options, config ]
- super
- end
-
- # Receives a name and invokes it. The name can be a string (either "task" or
- # "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task
- # cannot be guessed by name, it can also be supplied as second argument.
- #
- # You can also supply the arguments, options and configuration values for
- # the task to be invoked, if none is given, the same values used to
- # initialize the invoker are used to initialize the invoked.
- #
- # ==== Examples
- #
- # class A < Thor
- # def foo
- # invoke :bar
- # invoke "b:hello", ["José"]
- # end
- #
- # def bar
- # invoke "b:hello", ["José"]
- # end
- # end
- #
- # class B < Thor
- # def hello(name)
- # puts "hello #{name}"
- # end
- # end
- #
- # You can notice that the method "foo" above invokes two tasks: "bar",
- # which belongs to the same class and "hello" which belongs to the class B.
- #
- # By using an invocation system you ensure that a task is invoked only once.
- # In the example above, invoking "foo" will invoke "b:hello" just once, even
- # if it's invoked later by "bar" method.
- #
- # When class A invokes class B, all arguments used on A initialization are
- # supplied to B. This allows lazy parse of options. Let's suppose you have
- # some rspec tasks:
- #
- # class Rspec < Thor::Group
- # class_option :mock_framework, :type => :string, :default => :rr
- #
- # def invoke_mock_framework
- # invoke "rspec:#{options[:mock_framework]}"
- # end
- # end
- #
- # As you noticed, it invokes the given mock framework, which might have its
- # own options:
- #
- # class Rspec::RR < Thor::Group
- # class_option :style, :type => :string, :default => :mock
- # end
- #
- # Since it's not rspec concern to parse mock framework options, when RR
- # is invoked all options are parsed again, so RR can extract only the options
- # that it's going to use.
- #
- # If you want Rspec::RR to be initialized with its own set of options, you
- # have to do that explicitely:
- #
- # invoke "rspec:rr", [], :style => :foo
- #
- # Besides giving an instance, you can also give a class to invoke:
- #
- # invoke Rspec::RR, [], :style => :foo
- #
- def invoke(name=nil, task=nil, args=nil, opts=nil, config=nil)
- task, args, opts, config = nil, task, args, opts if task.nil? || task.is_a?(Array)
- args, opts, config = nil, args, opts if args.is_a?(Hash)
-
- object, task = _prepare_for_invocation(name, task)
- klass, instance = _initialize_klass_with_initializer(object, args, opts, config)
-
- method_args = []
- current = @_invocations[klass]
-
- iterator = proc do |_, task|
- unless current.include?(task.name)
- current << task.name
- task.run(instance, method_args)
- end
- end
-
- if task
- args ||= []
- method_args = args[Range.new(klass.arguments.size, -1)] || []
- iterator.call(nil, task)
- else
- klass.all_tasks.map(&iterator)
- end
- end
-
- protected
-
- # Configuration values that are shared between invocations.
- #
- def _shared_configuration #:nodoc:
- { :invocations => @_invocations }
- end
-
- # Prepare for invocation in the instance level. In this case, we have to
- # take into account that a just a task name from the current class was
- # given or even a Thor::Task object.
- #
- def _prepare_for_invocation(name, sent_task=nil) #:nodoc:
- if name.is_a?(Thor::Task)
- task = name
- elsif task = self.class.all_tasks[name.to_s]
- object = self
- else
- object, task = self.class.prepare_for_invocation(nil, name)
- task ||= sent_task
- end
-
- # If the object was not set, use self and use the name as task.
- object, task = self, name unless object
- return object, _validate_task(object, task)
- end
-
- # Check if the object given is a Thor class object and get a task object
- # for it.
- #
- def _validate_task(object, task) #:nodoc:
- klass = object.is_a?(Class) ? object : object.class
- raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
-
- task ||= klass.default_task if klass <= Thor
- task = klass.all_tasks[task.to_s] || Thor::Task::Dynamic.new(task) if task && !task.is_a?(Thor::Task)
- task
- end
-
- # Initialize klass using values stored in the @_initializer.
- #
- def _initialize_klass_with_initializer(object, args, opts, config) #:nodoc:
- if object.is_a?(Class)
- klass = object
-
- stored_args, stored_opts, stored_config = @_initializer
- args ||= stored_args.dup
- opts ||= stored_opts.dup
-
- config ||= {}
- config = stored_config.merge(_shared_configuration).merge!(config)
- [ klass, klass.new(args, opts, config) ]
- else
- [ object.class, object ]
- end
- end
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser.rb
deleted file mode 100644
index 57a3f6e1a5..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-require 'thor/parser/argument'
-require 'thor/parser/arguments'
-require 'thor/parser/option'
-require 'thor/parser/options'
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/argument.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/argument.rb
deleted file mode 100644
index aa8ace4719..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/argument.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-class Thor
- class Argument #:nodoc:
- VALID_TYPES = [ :numeric, :hash, :array, :string ]
-
- attr_reader :name, :description, :required, :type, :default, :banner
- alias :human_name :name
-
- def initialize(name, description=nil, required=true, type=:string, default=nil, banner=nil)
- class_name = self.class.name.split("::").last
-
- raise ArgumentError, "#{class_name} name can't be nil." if name.nil?
- raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type)
-
- @name = name.to_s
- @description = description
- @required = required || false
- @type = (type || :string).to_sym
- @default = default
- @banner = banner || default_banner
-
- validate! # Trigger specific validations
- end
-
- def usage
- required? ? banner : "[#{banner}]"
- end
-
- def required?
- required
- end
-
- def show_default?
- case default
- when Array, String, Hash
- !default.empty?
- else
- default
- end
- end
-
- protected
-
- def validate!
- raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil?
- end
-
- def valid_type?(type)
- VALID_TYPES.include?(type.to_sym)
- end
-
- def default_banner
- case type
- when :boolean
- nil
- when :string, :default
- human_name.upcase
- when :numeric
- "N"
- when :hash
- "key:value"
- when :array
- "one two three"
- end
- end
-
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/arguments.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/arguments.rb
deleted file mode 100644
index fb5d965e06..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/arguments.rb
+++ /dev/null
@@ -1,145 +0,0 @@
-class Thor
- class Arguments #:nodoc:
- NUMERIC = /(\d*\.\d+|\d+)/
-
- # Receives an array of args and returns two arrays, one with arguments
- # and one with switches.
- #
- def self.split(args)
- arguments = []
-
- args.each do |item|
- break if item =~ /^-/
- arguments << item
- end
-
- return arguments, args[Range.new(arguments.size, -1)]
- end
-
- def self.parse(base, args)
- new(base).parse(args)
- end
-
- # Takes an array of Thor::Argument objects.
- #
- def initialize(arguments=[])
- @assigns, @non_assigned_required = {}, []
- @switches = arguments
-
- arguments.each do |argument|
- if argument.default
- @assigns[argument.human_name] = argument.default
- elsif argument.required?
- @non_assigned_required << argument
- end
- end
- end
-
- def parse(args)
- @pile = args.dup
-
- @switches.each do |argument|
- break unless peek
- @non_assigned_required.delete(argument)
- @assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name)
- end
-
- check_requirement!
- @assigns
- end
-
- private
-
- def peek
- @pile.first
- end
-
- def shift
- @pile.shift
- end
-
- def unshift(arg)
- unless arg.kind_of?(Array)
- @pile.unshift(arg)
- else
- @pile = arg + @pile
- end
- end
-
- def current_is_value?
- peek && peek.to_s !~ /^-/
- end
-
- # Runs through the argument array getting strings that contains ":" and
- # mark it as a hash:
- #
- # [ "name:string", "age:integer" ]
- #
- # Becomes:
- #
- # { "name" => "string", "age" => "integer" }
- #
- def parse_hash(name)
- return shift if peek.is_a?(Hash)
- hash = {}
-
- while current_is_value? && peek.include?(?:)
- key, value = shift.split(':')
- hash[key] = value
- end
- hash
- end
-
- # Runs through the argument array getting all strings until no string is
- # found or a switch is found.
- #
- # ["a", "b", "c"]
- #
- # And returns it as an array:
- #
- # ["a", "b", "c"]
- #
- def parse_array(name)
- return shift if peek.is_a?(Array)
- array = []
-
- while current_is_value?
- array << shift
- end
- array
- end
-
- # Check if the peel is numeric ofrmat and return a Float or Integer.
- # Otherwise raises an error.
- #
- def parse_numeric(name)
- return shift if peek.is_a?(Numeric)
-
- unless peek =~ NUMERIC && $& == peek
- raise MalformattedArgumentError, "expected numeric value for '#{name}'; got #{peek.inspect}"
- end
-
- $&.index('.') ? shift.to_f : shift.to_i
- end
-
- # Parse string, i.e., just return the current value in the pile.
- #
- def parse_string(name)
- shift
- end
-
- # Raises an error if @non_assigned_required array is not empty.
- #
- def check_requirement!
- unless @non_assigned_required.empty?
- names = @non_assigned_required.map do |o|
- o.respond_to?(:switch_name) ? o.switch_name : o.human_name
- end.join("', '")
-
- class_name = self.class.name.split('::').last.downcase
- raise RequiredArgumentMissingError, "no value provided for required #{class_name} '#{names}'"
- end
- end
-
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/option.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/option.rb
deleted file mode 100644
index 9e40ec73fa..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/option.rb
+++ /dev/null
@@ -1,132 +0,0 @@
-class Thor
- class Option < Argument #:nodoc:
- attr_reader :aliases, :group
-
- VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
-
- def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, group=nil, aliases=nil)
- super(name, description, required, type, default, banner)
- @aliases = [*aliases].compact
- @group = group.to_s.capitalize if group
- end
-
- # This parse quick options given as method_options. It makes several
- # assumptions, but you can be more specific using the option method.
- #
- # parse :foo => "bar"
- # #=> Option foo with default value bar
- #
- # parse [:foo, :baz] => "bar"
- # #=> Option foo with default value bar and alias :baz
- #
- # parse :foo => :required
- # #=> Required option foo without default value
- #
- # parse :foo => 2
- # #=> Option foo with default value 2 and type numeric
- #
- # parse :foo => :numeric
- # #=> Option foo without default value and type numeric
- #
- # parse :foo => true
- # #=> Option foo with default value true and type boolean
- #
- # The valid types are :boolean, :numeric, :hash, :array and :string. If none
- # is given a default type is assumed. This default type accepts arguments as
- # string (--foo=value) or booleans (just --foo).
- #
- # By default all options are optional, unless :required is given.
- #
- def self.parse(key, value)
- if key.is_a?(Array)
- name, *aliases = key
- else
- name, aliases = key, []
- end
-
- name = name.to_s
- default = value
-
- type = case value
- when Symbol
- default = nil
-
- if VALID_TYPES.include?(value)
- value
- elsif required = (value == :required)
- :string
- elsif value == :optional
- # TODO Remove this warning in the future.
- warn "Optional type is deprecated. Choose :boolean or :string instead. Assumed to be :boolean."
- :boolean
- end
- when TrueClass, FalseClass
- :boolean
- when Numeric
- :numeric
- when Hash, Array, String
- value.class.name.downcase.to_sym
- end
-
- self.new(name.to_s, nil, required, type, default, nil, nil, aliases)
- end
-
- def switch_name
- @switch_name ||= dasherized? ? name : dasherize(name)
- end
-
- def human_name
- @human_name ||= dasherized? ? undasherize(name) : name
- end
-
- def usage(padding=0)
- sample = if banner && !banner.to_s.empty?
- "#{switch_name}=#{banner}"
- else
- switch_name
- end
-
- sample = "[#{sample}]" unless required?
-
- if aliases.empty?
- (" " * padding) << sample
- else
- "#{aliases.join(', ')}, #{sample}"
- end
- end
-
- # Allow some type predicates as: boolean?, string? and etc.
- #
- def method_missing(method, *args, &block)
- given = method.to_s.sub(/\?$/, '').to_sym
- if valid_type?(given)
- self.type == given
- else
- super
- end
- end
-
- protected
-
- def validate!
- raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
- end
-
- def valid_type?(type)
- VALID_TYPES.include?(type.to_sym)
- end
-
- def dasherized?
- name.index('-') == 0
- end
-
- def undasherize(str)
- str.sub(/^-{1,2}/, '')
- end
-
- def dasherize(str)
- (str.length > 1 ? "--" : "-") + str.gsub('_', '-')
- end
-
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/options.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/options.rb
deleted file mode 100644
index 75092308b5..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/options.rb
+++ /dev/null
@@ -1,142 +0,0 @@
-class Thor
- # This is a modified version of Daniel Berger's Getopt::Long class, licensed
- # under Ruby's license.
- #
- class Options < Arguments #:nodoc:
- LONG_RE = /^(--\w+[-\w+]*)$/
- SHORT_RE = /^(-[a-z])$/i
- EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i
- SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
- SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
-
- # Receives a hash and makes it switches.
- #
- def self.to_switches(options)
- options.map do |key, value|
- case value
- when true
- "--#{key}"
- when Array
- "--#{key} #{value.map{ |v| v.inspect }.join(' ')}"
- when Hash
- "--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}"
- when nil, false
- ""
- else
- "--#{key} #{value.inspect}"
- end
- end.join(" ")
- end
-
- # Takes a hash of Thor::Option objects.
- #
- def initialize(options={})
- options = options.values
- super(options)
- @shorts, @switches = {}, {}
-
- options.each do |option|
- @switches[option.switch_name] = option
-
- option.aliases.each do |short|
- @shorts[short.to_s] ||= option.switch_name
- end
- end
- end
-
- def parse(args)
- @pile = args.dup
-
- while peek
- if current_is_switch?
- case shift
- when SHORT_SQ_RE
- unshift($1.split('').map { |f| "-#{f}" })
- next
- when EQ_RE, SHORT_NUM
- unshift($2)
- switch = $1
- when LONG_RE, SHORT_RE
- switch = $1
- end
-
- switch = normalize_switch(switch)
- next unless option = switch_option(switch)
-
- @assigns[option.human_name] = parse_peek(switch, option)
- else
- shift
- end
- end
-
- check_requirement!
- @assigns
- end
-
- protected
-
- # Returns true if the current value in peek is a registered switch.
- #
- def current_is_switch?
- case peek
- when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
- switch?($1)
- when SHORT_SQ_RE
- $1.split('').any? { |f| switch?("-#{f}") }
- end
- end
-
- def switch?(arg)
- switch_option(arg) || @shorts.key?(arg)
- end
-
- def switch_option(arg)
- if match = no_or_skip?(arg)
- @switches[arg] || @switches["--#{match}"]
- else
- @switches[arg]
- end
- end
-
- def no_or_skip?(arg)
- arg =~ /^--(no|skip)-([-\w]+)$/
- $2
- end
-
- # Check if the given argument is actually a shortcut.
- #
- def normalize_switch(arg)
- @shorts.key?(arg) ? @shorts[arg] : arg
- end
-
- # Parse boolean values which can be given as --foo=true, --foo or --no-foo.
- #
- def parse_boolean(switch)
- if current_is_value?
- ["true", "TRUE", "t", "T", true].include?(shift)
- else
- @switches.key?(switch) || !no_or_skip?(switch)
- end
- end
-
- # Parse the value at the peek analyzing if it requires an input or not.
- #
- def parse_peek(switch, option)
- unless current_is_value?
- if option.boolean?
- # No problem for boolean types
- elsif no_or_skip?(switch)
- return nil # User set value to nil
- elsif option.string? && !option.required?
- return option.human_name # Return the option name
- else
- raise MalformattedArgumentError, "no value provided for option '#{switch}'"
- end
- end
-
- @non_assigned_required.delete(option)
- send(:"parse_#{option.type}", switch)
- end
-
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/rake_compat.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/rake_compat.rb
deleted file mode 100644
index 3ab6bb21f5..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/rake_compat.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'rake'
-
-class Thor
- # Adds a compatibility layer to your Thor classes which allows you to use
- # rake package tasks. For example, to use rspec rake tasks, one can do:
- #
- # require 'thor/rake_compat'
- #
- # class Default < Thor
- # include Thor::RakeCompat
- #
- # Spec::Rake::SpecTask.new(:spec) do |t|
- # t.spec_opts = ['--options', "spec/spec.opts"]
- # t.spec_files = FileList['spec/**/*_spec.rb']
- # end
- # end
- #
- module RakeCompat
- def self.rake_classes
- @rake_classes ||= []
- end
-
- def self.included(base)
- # Hack. Make rakefile point to invoker, so rdoc task is generated properly.
- Rake.application.instance_variable_set(:@rakefile, caller[0].match(/(.*):\d+/)[1])
- self.rake_classes << base
- end
- end
-end
-
-class Object #:nodoc:
- alias :rake_task :task
- alias :rake_namespace :namespace
-
- def task(*args, &block)
- task = rake_task(*args, &block)
-
- if klass = Thor::RakeCompat.rake_classes.last
- non_namespaced_name = task.name.split(':').last
-
- description = non_namespaced_name
- description << task.arg_names.map{ |n| n.to_s.upcase }.join(' ')
- description.strip!
-
- klass.desc description, task.comment || non_namespaced_name
- klass.class_eval <<-METHOD
- def #{non_namespaced_name}(#{task.arg_names.join(', ')})
- Rake::Task[#{task.name.to_sym.inspect}].invoke(#{task.arg_names.join(', ')})
- end
- METHOD
- end
-
- task
- end
-
- def namespace(name, &block)
- if klass = Thor::RakeCompat.rake_classes.last
- const_name = Thor::Util.camel_case(name.to_s).to_sym
- klass.const_set(const_name, Class.new(Thor))
- new_klass = klass.const_get(const_name)
- Thor::RakeCompat.rake_classes << new_klass
- end
-
- rake_namespace(name, &block)
- Thor::RakeCompat.rake_classes.pop
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/runner.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/runner.rb
deleted file mode 100644
index 43da09b336..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/runner.rb
+++ /dev/null
@@ -1,299 +0,0 @@
-require 'fileutils'
-require 'open-uri'
-require 'yaml'
-require 'digest/md5'
-require 'pathname'
-
-class Thor::Runner < Thor #:nodoc:
- map "-T" => :list, "-i" => :install, "-u" => :update
-
- # Override Thor#help so it can give information about any class and any method.
- #
- def help(meth=nil)
- if meth && !self.respond_to?(meth)
- initialize_thorfiles(meth)
- klass, task = Thor::Util.namespace_to_thor_class_and_task(meth)
- # Send mapping -h because it works with Thor::Group too
- klass.start(["-h", task].compact, :shell => self.shell)
- else
- super
- end
- end
-
- # If a task is not found on Thor::Runner, method missing is invoked and
- # Thor::Runner is then responsable for finding the task in all classes.
- #
- def method_missing(meth, *args)
- meth = meth.to_s
- initialize_thorfiles(meth)
- klass, task = Thor::Util.namespace_to_thor_class_and_task(meth)
- args.unshift(task) if task
- klass.start(args, :shell => shell)
- end
-
- desc "install NAME", "Install an optionally named Thor file into your system tasks"
- method_options :as => :string, :relative => :boolean
- def install(name)
- initialize_thorfiles
-
- # If a directory name is provided as the argument, look for a 'main.thor'
- # task in said directory.
- begin
- if File.directory?(File.expand_path(name))
- base, package = File.join(name, "main.thor"), :directory
- contents = open(base).read
- else
- base, package = name, :file
- contents = open(name).read
- end
- rescue OpenURI::HTTPError
- raise Error, "Error opening URI '#{name}'"
- rescue Errno::ENOENT
- raise Error, "Error opening file '#{name}'"
- end
-
- say "Your Thorfile contains:"
- say contents
-
- return false if no?("Do you wish to continue [y/N]?")
-
- as = options["as"] || begin
- first_line = contents.split("\n")[0]
- (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
- end
-
- unless as
- basename = File.basename(name)
- as = ask("Please specify a name for #{name} in the system repository [#{basename}]:")
- as = basename if as.empty?
- end
-
- location = if options[:relative] || name =~ /^http:\/\//
- name
- else
- File.expand_path(name)
- end
-
- thor_yaml[as] = {
- :filename => Digest::MD5.hexdigest(name + as),
- :location => location,
- :namespaces => Thor::Util.namespaces_in_content(contents, base)
- }
-
- save_yaml(thor_yaml)
- say "Storing thor file in your system repository"
- destination = File.join(thor_root, thor_yaml[as][:filename])
-
- if package == :file
- File.open(destination, "w") { |f| f.puts contents }
- else
- FileUtils.cp_r(name, destination)
- end
-
- thor_yaml[as][:filename] # Indicate success
- end
-
- desc "uninstall NAME", "Uninstall a named Thor module"
- def uninstall(name)
- raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
- say "Uninstalling #{name}."
- FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}"))
-
- thor_yaml.delete(name)
- save_yaml(thor_yaml)
-
- puts "Done."
- end
-
- desc "update NAME", "Update a Thor file from its original location"
- def update(name)
- raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location]
-
- say "Updating '#{name}' from #{thor_yaml[name][:location]}"
-
- old_filename = thor_yaml[name][:filename]
- self.options = self.options.merge("as" => name)
- filename = install(thor_yaml[name][:location])
-
- unless filename == old_filename
- File.delete(File.join(thor_root, old_filename))
- end
- end
-
- desc "installed", "List the installed Thor modules and tasks"
- method_options :internal => :boolean
- def installed
- initialize_thorfiles(nil, true)
-
- klasses = Thor::Base.subclasses
- klasses -= [Thor, Thor::Runner] unless options["internal"]
-
- display_klasses(true, klasses)
- end
-
- desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
- method_options :substring => :boolean, :group => :string, :all => :boolean
- def list(search="")
- initialize_thorfiles
-
- search = ".*#{search}" if options["substring"]
- search = /^#{search}.*/i
- group = options[:group] || "standard"
-
- klasses = Thor::Base.subclasses.select do |k|
- (options[:all] || k.group == group) && k.namespace =~ search
- end
-
- display_klasses(false, klasses)
- end
-
- private
-
- def thor_root
- Thor::Util.thor_root
- end
-
- def thor_yaml
- @thor_yaml ||= begin
- yaml_file = File.join(thor_root, "thor.yml")
- yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
- yaml || {}
- end
- end
-
- # Save the yaml file. If none exists in thor root, creates one.
- #
- def save_yaml(yaml)
- yaml_file = File.join(thor_root, "thor.yml")
-
- unless File.exists?(yaml_file)
- FileUtils.mkdir_p(thor_root)
- yaml_file = File.join(thor_root, "thor.yml")
- FileUtils.touch(yaml_file)
- end
-
- File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml }
- end
-
- def self.exit_on_failure?
- true
- end
-
- # Load the thorfiles. If relevant_to is supplied, looks for specific files
- # in the thor_root instead of loading them all.
- #
- # By default, it also traverses the current path until find Thor files, as
- # described in thorfiles. This look up can be skipped by suppliying
- # skip_lookup true.
- #
- def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
- thorfiles(relevant_to, skip_lookup).each do |f|
- Thor::Util.load_thorfile(f) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f))
- end
- end
-
- # Finds Thorfiles by traversing from your current directory down to the root
- # directory of your system. If at any time we find a Thor file, we stop.
- #
- # We also ensure that system-wide Thorfiles are loaded first, so local
- # Thorfiles can override them.
- #
- # ==== Example
- #
- # If we start at /Users/wycats/dev/thor ...
- #
- # 1. /Users/wycats/dev/thor
- # 2. /Users/wycats/dev
- # 3. /Users/wycats <-- we find a Thorfile here, so we stop
- #
- # Suppose we start at c:\Documents and Settings\james\dev\thor ...
- #
- # 1. c:\Documents and Settings\james\dev\thor
- # 2. c:\Documents and Settings\james\dev
- # 3. c:\Documents and Settings\james
- # 4. c:\Documents and Settings
- # 5. c:\ <-- no Thorfiles found!
- #
- def thorfiles(relevant_to=nil, skip_lookup=false)
- # Deal with deprecated thor when :namespaces: is available as constants
- save_yaml(thor_yaml) if Thor::Util.convert_constants_to_namespaces(thor_yaml)
-
- thorfiles = []
-
- unless skip_lookup
- Pathname.pwd.ascend do |path|
- thorfiles = Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten
- break unless thorfiles.empty?
- end
- end
-
- files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Thor::Util.thor_root_glob)
- files += thorfiles
- files -= ["#{thor_root}/thor.yml"]
-
- files.map! do |file|
- File.directory?(file) ? File.join(file, "main.thor") : file
- end
- end
-
- # Load thorfiles relevant to the given method. If you provide "foo:bar" it
- # will load all thor files in the thor.yaml that has "foo" e "foo:bar"
- # namespaces registered.
- #
- def thorfiles_relevant_to(meth)
- lookup = [ meth, meth.split(":")[0...-1].join(":") ]
-
- files = thor_yaml.select do |k, v|
- v[:namespaces] && !(v[:namespaces] & lookup).empty?
- end
-
- files.map { |k, v| File.join(thor_root, "#{v[:filename]}") }
- end
-
- # Display information about the given klasses. If with_module is given,
- # it shows a table with information extracted from the yaml file.
- #
- def display_klasses(with_modules=false, klasses=Thor.subclasses)
- klasses -= [Thor, Thor::Runner] unless with_modules
- raise Error, "No Thor tasks available" if klasses.empty?
-
- if with_modules && !thor_yaml.empty?
- info = []
- labels = ["Modules", "Namespaces"]
-
- info << labels
- info << [ "-" * labels[0].size, "-" * labels[1].size ]
-
- thor_yaml.each do |name, hash|
- info << [ name, hash[:namespaces].join(", ") ]
- end
-
- print_table info
- say ""
- end
-
- unless klasses.empty?
- klasses.dup.each do |klass|
- klasses -= Thor::Util.thor_classes_in(klass)
- end
-
- klasses.each { |k| display_tasks(k) }
- else
- say "\033[1;34mNo Thor tasks available\033[0m"
- end
- end
-
- # Display tasks from the given Thor class.
- #
- def display_tasks(klass)
- unless klass.tasks.empty?
- base = klass.namespace
-
- color = base == "default" ? :magenta : :blue
- say shell.set_color(base, color, true)
- say "-" * base.length
-
- klass.help(shell, :short => true, :ident => 0, :namespace => true)
- end
- end
-end
diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell.rb b/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell.rb
deleted file mode 100644
index 0d3f4d5951..0000000000
--- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-require 'thor/shell/color'
-
-class Thor
- module Base
- # Returns the shell used in all Thor classes. Default to color one.
- #
- def self.shell
- @shell ||= Thor::Shell::Color
- end
-
- # Sets the shell used in all Thor classes.
- #
- def self.shell=(klass)
- @shell = klass
- end
- end
-
- module Shell
- SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_list, :print_table]
-
- # Add shell to initialize config values.
- #
- # ==== Configuration
- # shell