actionpack 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

data/CHANGELOG CHANGED
@@ -1,3 +1,27 @@
1
+ *1.2.0* (January 4th, 2005)
2
+
3
+ * Added MemCacheStore for storing session data in Danga's MemCache system [Bob Cottrell]
4
+ Depends on: MemCached server (http://www.danga.com/memcached/), MemCache client (http://raa.ruby-lang.org/project/memcache/)
5
+
6
+ * Added thread-safety to the DRbStore #66, #389 [Ben Stiglitz]
7
+
8
+ * Added DateHelper#select_time and DateHelper#select_second #373 [Scott Baron]
9
+
10
+ * Added :host and :protocol options to url_for and friends to redirect to another host and protocol than the current.
11
+
12
+ * Added class declaration for the MissingFile exception #388 [Kent Sibilev]
13
+
14
+ * Added "short hypertext note with a hyperlink to the new URI(s)" to redirects to fulfill compliance with RFC 2616 (HTTP/1.1) section 10.3.3 #397 [Tim Bates]
15
+
16
+ * Added second boolean parameter to Base.redirect_to_url and Response#redirect to control whether the redirect is permanent or not (301 vs 302) #375 [Hodel]
17
+
18
+ * Fixed redirects when the controller and action is named the same. Still haven't fixed same controller, module, and action, though #201 [Josh]
19
+
20
+ * Fixed problems with running multiple functional tests in Rails under 1.8.2 by including hack for test/unit weirdness
21
+
22
+ * Fixed that @request.remote_ip didn't work in the test environment #369 [Bruno Mattarollo]
23
+
24
+
1
25
  *1.1.0*
2
26
 
3
27
  * Added search through session to clear out association caches at the end of each request. This makes it possible to place Active Record objects
data/install.rb CHANGED
@@ -40,8 +40,10 @@ files = %w-
40
40
  action_controller/benchmarking.rb
41
41
  action_controller/cgi_ext/cgi_ext.rb
42
42
  action_controller/cgi_ext/cgi_methods.rb
43
+ action_controller/cgi_ext/cookie_performance_fix.rb
43
44
  action_controller/cgi_process.rb
44
45
  action_controller/cookies.rb
46
+ action_controller/dependencies.rb
45
47
  action_controller/filters.rb
46
48
  action_controller/flash.rb
47
49
  action_controller/helpers.rb
@@ -53,11 +55,18 @@ files = %w-
53
55
  action_controller/session/active_record_store.rb
54
56
  action_controller/session/drb_server.rb
55
57
  action_controller/session/drb_store.rb
58
+ action_controller/session/mem_cache_store.rb
59
+ action_controller/session.rb
56
60
  action_controller/support/class_inheritable_attributes.rb
57
61
  action_controller/support/class_attribute_accessors.rb
58
62
  action_controller/support/clean_logger.rb
59
63
  action_controller/support/cookie_performance_fix.rb
60
64
  action_controller/support/inflector.rb
65
+ action_controller/support/binding_of_caller.rb
66
+ action_controller/support/breakpoint.rb
67
+ action_controller/support/dependencies.rb
68
+ action_controller/support/misc.rb
69
+ action_controller/support/module_attribute_accessors.rb
61
70
  action_controller/templates/rescues/_request_and_response.rhtml
62
71
  action_controller/templates/rescues/diagnostics.rhtml
63
72
  action_controller/templates/rescues/layout.rhtml
@@ -25,6 +25,7 @@ $:.unshift(File.dirname(__FILE__))
25
25
 
26
26
  require 'action_controller/support/clean_logger'
27
27
  require 'action_controller/support/misc'
28
+ require 'action_controller/support/dependencies'
28
29
 
29
30
  require 'action_controller/base'
30
31
  require 'action_controller/rescue'
@@ -14,6 +14,8 @@ module ActionController #:nodoc:
14
14
  end
15
15
  class UnknownAction < ActionControllerError #:nodoc:
16
16
  end
17
+ class MissingFile < ActionControllerError #:nodoc:
18
+ end
17
19
 
18
20
  # Action Controllers are made up of one or more actions that performs its purpose and then either renders a template or
19
21
  # redirects to another action. An action is defined as a public method on the controller, which will automatically be
@@ -432,7 +434,7 @@ module ActionController #:nodoc:
432
434
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
433
435
  # for the Cache-Control header spec.
434
436
  def send_file(path, options = {}) #:doc:
435
- raise MissingFile unless File.file?(path) and File.readable?(path)
437
+ raise MissingFile, path unless File.file?(path) and File.readable?(path)
436
438
 
437
439
  options[:length] ||= File.size(path)
438
440
  options[:filename] ||= File.basename(path)
@@ -529,10 +531,11 @@ module ActionController #:nodoc:
529
531
  end
530
532
 
531
533
  # Redirects the browser to the specified <tt>url</tt>. Used to redirect outside of the current application. Example:
532
- # <tt>redirect_to_url "http://www.rubyonrails.org"</tt>.
533
- def redirect_to_url(url) #:doc:
534
+ # <tt>redirect_to_url "http://www.rubyonrails.org"</tt>. If the resource has moved permanently, it's possible to pass true as the
535
+ # second parameter and the browser will get "301 Moved Permanently" instead of "302 Found".
536
+ def redirect_to_url(url, permanently = false) #:doc:
534
537
  logger.info("Redirected to #{url}") unless logger.nil?
535
- @response.redirect(url)
538
+ @response.redirect(url, permanently)
536
539
  @performed_redirect = true
537
540
  end
538
541
 
@@ -1,5 +1,5 @@
1
1
  require 'action_controller/cgi_ext/cgi_ext'
2
- require 'action_controller/support/cookie_performance_fix'
2
+ require 'action_controller/cgi_ext/cookie_performance_fix'
3
3
  require 'action_controller/session/drb_store'
4
4
  require 'action_controller/session/active_record_store'
5
5
 
@@ -1,22 +1,8 @@
1
- unless Object.respond_to?(:require_dependency)
2
- Object.send(:define_method, :require_dependency) { |file_name| ActionController::Base.require_dependency(file_name) }
3
- end
4
-
5
1
  module ActionController #:nodoc:
6
2
  module Dependencies #:nodoc:
7
3
  def self.append_features(base)
8
4
  super
9
-
10
- base.class_eval do
11
- # When turned on (which is default), all dependencies are included using "load". This mean that any change is instant in cached
12
- # environments like mod_ruby or FastCGI. When set to false, "require" is used, which is faster but requires server restart to
13
- # be effective.
14
- @@reload_dependencies = true
15
- cattr_accessor :reload_dependencies
16
- end
17
-
18
5
  base.class_eval { class << self; alias_method :inherited_without_model, :inherited; end }
19
-
20
6
  base.extend(ClassMethods)
21
7
  end
22
8
 
@@ -41,11 +27,6 @@ module ActionController #:nodoc:
41
27
  # # helper :post (already required)
42
28
  # end
43
29
  module ClassMethods
44
- # Loads the <tt>file_name</tt> if reload_dependencies is true or requires if it's false.
45
- def require_dependency(file_name)
46
- reload_dependencies ? silence_warnings { load("#{file_name}.rb") } : require(file_name)
47
- end
48
-
49
30
  # Specifies a variable number of models that this controller depends on. Models are normally Active Record classes or a similar
50
31
  # backend for modelling entity classes.
51
32
  def model(*models)
@@ -96,7 +96,7 @@ module ActionController #:nodoc:
96
96
  inherited_without_helper(child)
97
97
  begin
98
98
  child.helper(child.controller_name)
99
- rescue LoadError
99
+ rescue LoadError, StandardError
100
100
  # No default helper available for this controller
101
101
  end
102
102
  end
@@ -115,6 +115,22 @@ module ActionController #:nodoc:
115
115
  # a given action without a layout. Just use the method <tt>render_without_layout</tt>, which works just like Base.render --
116
116
  # it just doesn't apply any layouts.
117
117
  #
118
+ # == Using a different layout in the action render call
119
+ #
120
+ # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
121
+ # Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
122
+ # This is possible using <tt>render_with_layout</tt> method. It's just a bit more manual work as you'll have to supply fully
123
+ # qualified template and layout names as this example shows:
124
+ #
125
+ # class WeblogController < ActionController::Base
126
+ # def help
127
+ # render_with_layout "help/index", "200", "layouts/help"
128
+ # end
129
+ # end
130
+ #
131
+ # As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout
132
+ # as the third.
133
+ #
118
134
  # == Automatic layout assignment
119
135
  #
120
136
  # If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
@@ -7,9 +7,11 @@ module ActionController
7
7
  @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
8
8
  end
9
9
 
10
- def redirect(to_url)
11
- @headers["Status"] = "302 Moved"
10
+ def redirect(to_url, permanently = false)
11
+ @headers["Status"] = permanently ? "301 Moved Permanently" : "302 Found"
12
12
  @headers["location"] = to_url
13
+
14
+ @body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
13
15
  end
14
16
  end
15
17
  end
@@ -5,5 +5,28 @@
5
5
 
6
6
  require 'drb'
7
7
 
8
- DRb.start_service('druby://127.0.0.1:9192', Hash.new)
8
+ session_hash = Hash.new
9
+ session_hash.instance_eval { @mutex = Mutex.new }
10
+
11
+ class <<session_hash
12
+ def []=(key, value)
13
+ @mutex.synchronize do
14
+ super(key, value)
15
+ end
16
+ end
17
+
18
+ def [](key)
19
+ @mutex.synchronize do
20
+ super(key)
21
+ end
22
+ end
23
+
24
+ def delete(key)
25
+ @mutex.synchronize do
26
+ super(key)
27
+ end
28
+ end
29
+ end
30
+
31
+ DRb.start_service('druby://127.0.0.1:9192', session_hash)
9
32
  DRb.thread.join
@@ -0,0 +1,95 @@
1
+ # cgi/session/memcached.rb - persistent storage of marshalled session data
2
+ #
3
+ # == Overview
4
+ #
5
+ # This file provides the CGI::Session::MemCache class, which builds
6
+ # persistence of storage data on top of the MemCache library. See
7
+ # cgi/session.rb for more details on session storage managers.
8
+ #
9
+
10
+ begin
11
+ require 'cgi/session'
12
+ require 'memcache'
13
+
14
+ class CGI
15
+ class Session
16
+ # MemCache-based session storage class.
17
+ #
18
+ # This builds upon the top-level MemCache class provided by the
19
+ # library file memcache.rb. Session data is marshalled and stored
20
+ # in a memcached cache.
21
+ class MemCacheStore
22
+ def check_id(id) #:nodoc:#
23
+ /[^0-9a-zA-Z]+/ =~ id.to_s ? false : true
24
+ end
25
+
26
+ # Create a new CGI::Session::MemCache instance
27
+ #
28
+ # This constructor is used internally by CGI::Session. The
29
+ # user does not generally need to call it directly.
30
+ #
31
+ # +session+ is the session for which this instance is being
32
+ # created. The session id must only contain alphanumeric
33
+ # characters; automatically generated session ids observe
34
+ # this requirement.
35
+ #
36
+ # +option+ is a hash of options for the initialiser. The
37
+ # following options are recognized:
38
+ #
39
+ # cache:: an instance of a MemCache client to use as the
40
+ # session cache.
41
+ #
42
+ # This session's memcache entry will be created if it does
43
+ # not exist, or retrieved if it does.
44
+ def initialize(session, options = {})
45
+ id = session.session_id
46
+ unless check_id(id)
47
+ raise ArgumentError, "session_id '%s' is invalid" % id
48
+ end
49
+ @cache = options['cache']
50
+ @session_key = "session:#{id}"
51
+ @hash = {}
52
+ end
53
+
54
+ # Restore session state from the session's memcache entry.
55
+ #
56
+ # Returns the session state as a hash.
57
+ def restore
58
+ begin
59
+ @hash = @cache[@session_key]
60
+ rescue
61
+ # Ignore session get failures.
62
+ end
63
+ @hash = {} unless @hash
64
+ @hash
65
+ end
66
+
67
+ # Save session state to the session's memcache entry.
68
+ def update
69
+ begin
70
+ @cache[@session_key] = @hash
71
+ rescue
72
+ # Ignore session update failures.
73
+ end
74
+ end
75
+
76
+ # Update and close the session's memcache entry.
77
+ def close
78
+ update
79
+ end
80
+
81
+ # Delete the session's memcache entry.
82
+ def delete
83
+ begin
84
+ @cache.delete(@session_key)
85
+ rescue
86
+ # Ignore session delete failures.
87
+ end
88
+ @hash = {}
89
+ end
90
+ end
91
+ end
92
+ end
93
+ rescue LoadError
94
+ # MemCache wasn't available so neither can the store be
95
+ end
@@ -0,0 +1,81 @@
1
+ begin
2
+ require 'simplecc'
3
+ rescue LoadError
4
+ def Continuation.create(*args, &block)
5
+ cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
6
+ result ||= args
7
+ return *[cc, *result]
8
+ end
9
+ end
10
+
11
+ # This method returns the binding of the method that called your
12
+ # method. It will raise an Exception when you're not inside a method.
13
+ #
14
+ # It's used like this:
15
+ # def inc_counter(amount = 1)
16
+ # Binding.of_caller do |binding|
17
+ # # Create a lambda that will increase the variable 'counter'
18
+ # # in the caller of this method when called.
19
+ # inc = eval("lambda { |arg| counter += arg }", binding)
20
+ # # We can refer to amount from inside this block safely.
21
+ # inc.call(amount)
22
+ # end
23
+ # # No other statements can go here. Put them inside the block.
24
+ # end
25
+ # counter = 0
26
+ # 2.times { inc_counter }
27
+ # counter # => 2
28
+ #
29
+ # Binding.of_caller must be the last statement in the method.
30
+ # This means that you will have to put everything you want to
31
+ # do after the call to Binding.of_caller into the block of it.
32
+ # This should be no problem however, because Ruby has closures.
33
+ # If you don't do this an Exception will be raised. Because of
34
+ # the way that Binding.of_caller is implemented it has to be
35
+ # done this way.
36
+ def Binding.of_caller(&block)
37
+ old_critical = Thread.critical
38
+ Thread.critical = true
39
+ count = 0
40
+ cc, result, error, extra_data = Continuation.create(nil, nil)
41
+ error.call if error
42
+
43
+ tracer = lambda do |*args|
44
+ type, context, extra_data = args[0], args[4], args
45
+ if type == "return"
46
+ count += 1
47
+ # First this method and then calling one will return --
48
+ # the trace event of the second event gets the context
49
+ # of the method which called the method that called this
50
+ # method.
51
+ if count == 2
52
+ # It would be nice if we could restore the trace_func
53
+ # that was set before we swapped in our own one, but
54
+ # this is impossible without overloading set_trace_func
55
+ # in current Ruby.
56
+ set_trace_func(nil)
57
+ cc.call(eval("binding", context), nil, extra_data)
58
+ end
59
+ elsif type == "line" then
60
+ nil
61
+ elsif type == "c-return" and extra_data[3] == :set_trace_func then
62
+ nil
63
+ else
64
+ set_trace_func(nil)
65
+ error_msg = "Binding.of_caller used in non-method context or " +
66
+ "trailing statements of method using it aren't in the block."
67
+ cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
68
+ end
69
+ end
70
+
71
+ unless result
72
+ set_trace_func(tracer)
73
+ return nil
74
+ else
75
+ Thread.critical = old_critical
76
+ case block.arity
77
+ when 1 then yield(result)
78
+ else yield(result, extra_data)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,525 @@
1
+ # The Breakpoint library provides the convenience of
2
+ # being able to inspect and modify state, diagnose
3
+ # bugs all via IRB by simply setting breakpoints in
4
+ # your applications by the call of a method.
5
+ #
6
+ # This library was written and is supported by me,
7
+ # Florian Gross. I can be reached at flgr@ccan.de
8
+ # and enjoy getting feedback about my libraries.
9
+ #
10
+ # The whole library (including breakpoint_client.rb
11
+ # and binding_of_caller.rb) is licensed under the
12
+ # same license that Ruby uses. (Which is currently
13
+ # either the GNU General Public License or a custom
14
+ # one that allows for commercial usage.) If you for
15
+ # some good reason need to use this under another
16
+ # license please contact me.
17
+
18
+ require 'irb'
19
+ # require 'binding_of_caller' <- Needs this
20
+ require 'drb'
21
+ require 'drb/acl'
22
+
23
+ module Breakpoint
24
+ extend self
25
+
26
+ # This will pop up an interactive ruby session at a
27
+ # pre-defined break point in a Ruby application. In
28
+ # this session you can examine the environment of
29
+ # the break point.
30
+ #
31
+ # You can get a list of variables in the context using
32
+ # local_variables via +local_variables+. You can then
33
+ # examine their values by typing their names.
34
+ #
35
+ # You can have a look at the call stack via +caller+.
36
+ #
37
+ # The source code around the location where the breakpoint
38
+ # was executed can be examined via +source_lines+. Its
39
+ # argument specifies how much lines of context to display.
40
+ # The default amount of context is 5 lines. Note that
41
+ # the call to +source_lines+ can raise an exception when
42
+ # it isn't able to read in the source code.
43
+ #
44
+ # breakpoints can also return a value. They will execute
45
+ # a supplied block for getting a default return value.
46
+ # A custom value can be returned from the session by doing
47
+ # +throw(:debug_return, value)+.
48
+ #
49
+ # You can also give names to break points which will be
50
+ # used in the message that is displayed upon execution
51
+ # of them.
52
+ #
53
+ # Here's a sample of how breakpoints should be placed:
54
+ #
55
+ # class Person
56
+ # def initialize(name, age)
57
+ # @name, @age = name, age
58
+ # breakpoint("Person#initialize")
59
+ # end
60
+ #
61
+ # attr_reader :age
62
+ # def name
63
+ # breakpoint("Person#name") { @name }
64
+ # end
65
+ # end
66
+ #
67
+ # person = Person.new("Random Person", 23)
68
+ # puts "Name: #{person.name}"
69
+ #
70
+ # And here is a sample debug session:
71
+ #
72
+ # Executing break point "Person#initialize" at file.rb:4 in `initialize'
73
+ # irb(#<Person:0x292fbe8>):001:0> local_variables
74
+ # => ["name", "age", "_", "__"]
75
+ # irb(#<Person:0x292fbe8>):002:0> [name, age]
76
+ # => ["Random Person", 23]
77
+ # irb(#<Person:0x292fbe8>):003:0> [@name, @age]
78
+ # => ["Random Person", 23]
79
+ # irb(#<Person:0x292fbe8>):004:0> self
80
+ # => #<Person:0x292fbe8 @age=23, @name="Random Person">
81
+ # irb(#<Person:0x292fbe8>):005:0> @age += 1; self
82
+ # => #<Person:0x292fbe8 @age=24, @name="Random Person">
83
+ # irb(#<Person:0x292fbe8>):006:0> exit
84
+ # Executing break point "Person#name" at file.rb:9 in `name'
85
+ # irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
86
+ # Name: Overriden name
87
+ #
88
+ # Breakpoint sessions will automatically have a few
89
+ # convenience methods available. See Breakpoint::CommandBundle
90
+ # for a list of them.
91
+ #
92
+ # Breakpoints can also be used remotely over sockets.
93
+ # This is implemented by running part of the IRB session
94
+ # in the application and part of it in a special client.
95
+ # You have to call Breakpoint.activate_drb to enable
96
+ # support for remote breakpoints and then run
97
+ # breakpoint_client.rb which is distributed with this
98
+ # library. See the documentation of Breakpoint.activate_drb
99
+ # for details.
100
+ def breakpoint(id = nil, context = nil, &block)
101
+ callstack = caller
102
+ callstack.slice!(0, 3) if callstack.first["breakpoint"]
103
+ file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
104
+
105
+ message = "Executing break point " + (id ? "#{id.inspect} " : "") +
106
+ "at #{file}:#{line}" + (method ? " in `#{method}'" : "")
107
+
108
+ if context then
109
+ return handle_breakpoint(context, message, file, line, &block)
110
+ end
111
+
112
+ Binding.of_caller do |binding_context|
113
+ handle_breakpoint(binding_context, message, file, line, &block)
114
+ end
115
+ end
116
+
117
+ module CommandBundle #:nodoc:
118
+ # Proxy to a Breakpoint client. Lets you directly execute code
119
+ # in the context of the client.
120
+ class Client#:nodoc:
121
+ def initialize(eval_handler) # :nodoc:
122
+ @eval_handler = eval_handler
123
+ end
124
+
125
+ instance_methods.each do |method|
126
+ next if method[/^__.+__$/]
127
+ undef_method method
128
+ end
129
+
130
+ # Executes the specified code at the client.
131
+ def eval(code)
132
+ @eval_handler.call(code)
133
+ end
134
+
135
+ # Will execute the specified statement at the client.
136
+ def method_missing(method, *args)
137
+ if args.empty?
138
+ result = eval("#{method}")
139
+ else
140
+ result = eval("#{method}(*Marshal.load(#{Marshal.dump(args).inspect}))")
141
+ end
142
+
143
+ unless [true, false, nil].include?(result)
144
+ result.extend(DRbUndumped) if result
145
+ end
146
+
147
+ return result
148
+ end
149
+ end
150
+
151
+ # Returns the source code surrounding the location where the
152
+ # breakpoint was issued.
153
+ def source_lines(context = 5, return_line_numbers = false)
154
+ lines = File.readlines(@__bp_file).map { |line| line.chomp }
155
+
156
+ break_line = @__bp_line
157
+ start_line = [break_line - context, 1].max
158
+ end_line = break_line + context
159
+
160
+ result = lines[(start_line - 1) .. (end_line - 1)]
161
+
162
+ if return_line_numbers then
163
+ return [start_line, break_line, result]
164
+ else
165
+ return result
166
+ end
167
+ end
168
+
169
+ # Lets an object that will forward method calls to the breakpoint
170
+ # client. This is useful for outputting longer things at the client
171
+ # and so on. You can for example do these things:
172
+ #
173
+ # client.puts "Hello" # outputs "Hello" at client console
174
+ # # outputs "Hello" into the file temp.txt at the client
175
+ # client.File.open("temp.txt", "w") { |f| f.puts "Hello" }
176
+ def client()
177
+ if Breakpoint.use_drb? then
178
+ Client.new(Breakpoint.drb_service.eval_handler)
179
+ else
180
+ Client.new(lambda { |code| eval(code, TOPLEVEL_BINDING) })
181
+ end
182
+ end
183
+ end
184
+
185
+ def handle_breakpoint(context, message, file = "", line = "", &block) # :nodoc:
186
+ catch(:debug_return) do |value|
187
+ eval(%{
188
+ @__bp_file = #{file.inspect}
189
+ @__bp_line = #{line}
190
+ extend Breakpoint::CommandBundle
191
+ extend DRbUndumped if self
192
+ }, context) rescue nil
193
+
194
+ if not use_drb? then
195
+ puts message
196
+ IRB.start(nil, IRB::WorkSpace.new(context))
197
+ else
198
+ @drb_service.add_breakpoint(context, message)
199
+ end
200
+
201
+ block.call if block
202
+ end
203
+ end
204
+
205
+ # These exceptions will be raised on failed asserts
206
+ # if Breakpoint.asserts_cause_exceptions is set to
207
+ # true.
208
+ class FailedAssertError < RuntimeError#:nodoc:
209
+ end
210
+
211
+ # This asserts that the block evaluates to true.
212
+ # If it doesn't evaluate to true a breakpoint will
213
+ # automatically be created at that execution point.
214
+ #
215
+ # You can disable assert checking in production
216
+ # code by setting Breakpoint.optimize_asserts to
217
+ # true. (It will still be enabled when Ruby is run
218
+ # via the -d argument.)
219
+ #
220
+ # Example:
221
+ # person_name = "Foobar"
222
+ # assert { not person_name.nil? }
223
+ #
224
+ # Note: If you want to use this method from an
225
+ # unit test, you will have to call it by its full
226
+ # name, Breakpoint.assert.
227
+ def assert(context = nil, &condition)
228
+ return if Breakpoint.optimize_asserts and not $DEBUG
229
+ return if yield
230
+
231
+ callstack = caller
232
+ callstack.slice!(0, 3) if callstack.first["assert"]
233
+ file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
234
+
235
+ message = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}."
236
+
237
+ if Breakpoint.asserts_cause_exceptions and not $DEBUG then
238
+ raise(Breakpoint::FailedAssertError, message)
239
+ end
240
+
241
+ message += " Executing implicit breakpoint."
242
+
243
+ if context then
244
+ return handle_breakpoint(context, message, file, line)
245
+ end
246
+
247
+ Binding.of_caller do |context|
248
+ handle_breakpoint(context, message, file, line)
249
+ end
250
+ end
251
+
252
+ # Whether asserts should be ignored if not in debug mode.
253
+ # Debug mode can be enabled by running ruby with the -d
254
+ # switch or by setting $DEBUG to true.
255
+ attr_accessor :optimize_asserts
256
+ self.optimize_asserts = false
257
+
258
+ # Whether an Exception should be raised on failed asserts
259
+ # in non-$DEBUG code or not. By default this is disabled.
260
+ attr_accessor :asserts_cause_exceptions
261
+ self.asserts_cause_exceptions = false
262
+ @use_drb = false
263
+
264
+ attr_reader :drb_service # :nodoc:
265
+
266
+ class DRbService # :nodoc:
267
+ include DRbUndumped
268
+
269
+ def initialize
270
+ @handler = @eval_handler = @collision_handler = nil
271
+
272
+ IRB.instance_eval { @CONF[:RC] = true }
273
+ IRB.run_config
274
+ end
275
+
276
+ def collision
277
+ sleep(0.5) until @collision_handler
278
+
279
+ @collision_handler.call
280
+ end
281
+
282
+ def ping; end
283
+
284
+ def add_breakpoint(context, message)
285
+ workspace = IRB::WorkSpace.new(context)
286
+ workspace.extend(DRbUndumped)
287
+
288
+ sleep(0.5) until @handler
289
+
290
+ @handler.call(workspace, message)
291
+ end
292
+
293
+ def register_handler(&block)
294
+ @handler = block
295
+ end
296
+
297
+ def unregister_handler
298
+ @handler = nil
299
+ end
300
+
301
+ attr_reader :eval_handler
302
+
303
+ def register_eval_handler(&block)
304
+ @eval_handler = block
305
+ end
306
+
307
+ def unregister_eval_handler
308
+ @eval_handler = lambda { }
309
+ end
310
+
311
+ def register_collision_handler(&block)
312
+ @collision_handler = block
313
+ end
314
+
315
+ def unregister_collision_handler
316
+ @collision_handler = lambda { }
317
+ end
318
+ end
319
+
320
+ # Will run Breakpoint in DRb mode. This will spawn a server
321
+ # that can be attached to via the breakpoint-client command
322
+ # whenever a breakpoint is executed. This is useful when you
323
+ # are debugging CGI applications or other applications where
324
+ # you can't access debug sessions via the standard input and
325
+ # output of your application.
326
+ #
327
+ # You can specify an URI where the DRb server will run at.
328
+ # This way you can specify the port the server runs on. The
329
+ # default URI is druby://localhost:42531.
330
+ #
331
+ # Please note that breakpoints will be skipped silently in
332
+ # case the DRb server can not spawned. (This can happen if
333
+ # the port is already used by another instance of your
334
+ # application on CGI or another application.)
335
+ #
336
+ # Also note that by default this will only allow access
337
+ # from localhost. You can however specify a list of
338
+ # allowed hosts or nil (to allow access from everywhere).
339
+ # But that will still not protect you from somebody
340
+ # reading the data as it goes through the net.
341
+ #
342
+ # A good approach for getting security and remote access
343
+ # is setting up an SSH tunnel between the DRb service
344
+ # and the client. This is usually done like this:
345
+ #
346
+ # $ ssh -L20000:127.0.0.1:20000 -R10000:127.0.0.1:10000 example.com
347
+ # (This will connect port 20000 at the client side to port
348
+ # 20000 at the server side, and port 10000 at the server
349
+ # side to port 10000 at the client side.)
350
+ #
351
+ # After that do this on the server side: (the code being debugged)
352
+ # Breakpoint.activate_drb("druby://127.0.0.1:20000", "localhost")
353
+ #
354
+ # And at the client side:
355
+ # ruby breakpoint_client.rb -c druby://127.0.0.1:10000 -s druby://127.0.0.1:20000
356
+ #
357
+ # Running through such a SSH proxy will also let you use
358
+ # breakpoint.rb in case you are behind a firewall.
359
+ #
360
+ # Detailed information about running DRb through firewalls is
361
+ # available at http://www.rubygarden.org/ruby?DrbTutorial
362
+ def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'], ignore_collisions = false) #:nodoc:
363
+
364
+ return false if @use_drb
365
+
366
+ uri ||= 'druby://localhost:42531'
367
+
368
+ if allowed_hosts then
369
+ acl = ["deny", "all"]
370
+
371
+ Array(allowed_hosts).each do |host|
372
+ acl += ["allow", host]
373
+ end
374
+
375
+ DRb.install_acl(ACL.new(acl))
376
+ end
377
+
378
+ @use_drb = true
379
+ @drb_service = DRbService.new
380
+ did_collision = false
381
+ begin
382
+ @service = DRb.start_service(uri, @drb_service)
383
+ rescue Errno::EADDRINUSE
384
+ if ignore_collisions then
385
+ nil
386
+ else
387
+ # The port is already occupied by another
388
+ # Breakpoint service. We will try to tell
389
+ # the old service that we want its port.
390
+ # It will then forward that request to the
391
+ # user and retry.
392
+ unless did_collision then
393
+ DRbObject.new(nil, uri).collision
394
+ did_collision = true
395
+ end
396
+ sleep(10)
397
+ retry
398
+ end
399
+ end
400
+
401
+ return true
402
+ end
403
+
404
+ # Deactivates a running Breakpoint service.
405
+ def deactivate_drb #:nodoc:
406
+ @service.stop_service unless @service.nil?
407
+ @service = nil
408
+ @use_drb = false
409
+ @drb_service = nil
410
+ end
411
+
412
+ # Returns true when Breakpoints are used over DRb.
413
+ # Breakpoint.activate_drb causes this to be true.
414
+ def use_drb? #:nodoc:
415
+ @use_drb == true
416
+ end
417
+ end
418
+
419
+ module IRB # :nodoc:
420
+ class << self; remove_method :start; end
421
+ def self.start(ap_path = nil, main_context = nil, workspace = nil)
422
+ $0 = File::basename(ap_path, ".rb") if ap_path
423
+
424
+ # suppress some warnings about redefined constants
425
+ old_verbose, $VERBOSE = $VERBOSE, nil
426
+ IRB.setup(ap_path)
427
+ $VERBOSE = old_verbose
428
+
429
+ if @CONF[:SCRIPT] then
430
+ irb = Irb.new(main_context, @CONF[:SCRIPT])
431
+ else
432
+ irb = Irb.new(main_context)
433
+ end
434
+
435
+ if workspace then
436
+ irb.context.workspace = workspace
437
+ end
438
+
439
+ @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
440
+ @CONF[:MAIN_CONTEXT] = irb.context
441
+
442
+ old_sigint = trap("SIGINT") do
443
+ irb.signal_handle
444
+ end
445
+
446
+ catch(:IRB_EXIT) do
447
+ irb.eval_input
448
+ end
449
+ ensure
450
+ trap("SIGINT", old_sigint)
451
+ end
452
+
453
+ class << self
454
+ alias :old_CurrentContext :CurrentContext
455
+ remove_method :CurrentContext
456
+ end
457
+ def IRB.CurrentContext
458
+ if old_CurrentContext.nil? and Breakpoint.use_drb? then
459
+ result = Object.new
460
+ def result.last_value; end
461
+ return result
462
+ else
463
+ old_CurrentContext
464
+ end
465
+ end
466
+
467
+ class Context#:nodoc:
468
+ alias :old_evaluate :evaluate
469
+ def evaluate(line, line_no)
470
+ if line.chomp == "exit" then
471
+ exit
472
+ else
473
+ old_evaluate(line, line_no)
474
+ end
475
+ end
476
+ end
477
+
478
+ class WorkSpace#:nodoc:
479
+ alias :old_evaluate :evaluate
480
+
481
+ def evaluate(*args)
482
+ if Breakpoint.use_drb? then
483
+ result = old_evaluate(*args)
484
+ if args[0] != :no_proxy and
485
+ not [true, false, nil].include?(result)
486
+ then
487
+ result.extend(DRbUndumped) rescue nil
488
+ end
489
+ return result
490
+ else
491
+ old_evaluate(*args)
492
+ end
493
+ end
494
+ end
495
+
496
+ module InputCompletor#:nodoc:
497
+ def self.eval(code, context, *more)
498
+ # Big hack, this assumes that InputCompletor
499
+ # will only call eval() when it wants code
500
+ # to be executed in the IRB context.
501
+ IRB.conf[:MAIN_CONTEXT].workspace.evaluate(:no_proxy, code, *more)
502
+ end
503
+ end
504
+ end
505
+
506
+ module DRb # :nodoc:
507
+ class DRbObject#:nodoc:
508
+ undef :inspect
509
+ undef :clone
510
+ end
511
+ end
512
+
513
+ # See Breakpoint.breakpoint
514
+ def breakpoint(id = nil, &block)
515
+ Binding.of_caller do |context|
516
+ Breakpoint.breakpoint(id, context, &block)
517
+ end
518
+ end
519
+
520
+ # See Breakpoint.assert
521
+ def assert(&block)
522
+ Binding.of_caller do |context|
523
+ Breakpoint.assert(context, &block)
524
+ end
525
+ end