activerecord 1.4.0 → 1.5.0

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

Potentially problematic release.


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

Files changed (55) hide show
  1. data/CHANGELOG +98 -0
  2. data/install.rb +1 -0
  3. data/lib/active_record.rb +1 -0
  4. data/lib/active_record/acts/list.rb +19 -16
  5. data/lib/active_record/associations.rb +164 -164
  6. data/lib/active_record/associations/association_collection.rb +44 -71
  7. data/lib/active_record/associations/association_proxy.rb +76 -0
  8. data/lib/active_record/associations/belongs_to_association.rb +74 -0
  9. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +34 -21
  10. data/lib/active_record/associations/has_many_association.rb +34 -30
  11. data/lib/active_record/associations/has_one_association.rb +48 -0
  12. data/lib/active_record/base.rb +62 -18
  13. data/lib/active_record/callbacks.rb +17 -8
  14. data/lib/active_record/connection_adapters/abstract_adapter.rb +11 -10
  15. data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
  16. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -1
  17. data/lib/active_record/connection_adapters/sqlite_adapter.rb +94 -73
  18. data/lib/active_record/deprecated_associations.rb +46 -8
  19. data/lib/active_record/fixtures.rb +1 -1
  20. data/lib/active_record/observer.rb +5 -1
  21. data/lib/active_record/support/binding_of_caller.rb +72 -68
  22. data/lib/active_record/support/breakpoint.rb +526 -524
  23. data/lib/active_record/support/class_inheritable_attributes.rb +105 -29
  24. data/lib/active_record/support/core_ext.rb +1 -0
  25. data/lib/active_record/support/core_ext/hash.rb +5 -0
  26. data/lib/active_record/support/core_ext/hash/keys.rb +35 -0
  27. data/lib/active_record/support/core_ext/numeric.rb +7 -0
  28. data/lib/active_record/support/core_ext/numeric/bytes.rb +33 -0
  29. data/lib/active_record/support/core_ext/numeric/time.rb +59 -0
  30. data/lib/active_record/support/core_ext/string.rb +5 -0
  31. data/lib/active_record/support/core_ext/string/inflections.rb +41 -0
  32. data/lib/active_record/support/dependencies.rb +1 -14
  33. data/lib/active_record/support/inflector.rb +6 -6
  34. data/lib/active_record/support/misc.rb +0 -24
  35. data/lib/active_record/validations.rb +34 -1
  36. data/lib/active_record/vendor/mysql411.rb +305 -0
  37. data/rakefile +11 -2
  38. data/test/abstract_unit.rb +1 -2
  39. data/test/associations_test.rb +234 -23
  40. data/test/base_test.rb +50 -1
  41. data/test/callbacks_test.rb +16 -0
  42. data/test/connections/native_mysql/connection.rb +2 -2
  43. data/test/connections/native_sqlite3/connection.rb +34 -0
  44. data/test/deprecated_associations_test.rb +36 -2
  45. data/test/fixtures/company.rb +2 -0
  46. data/test/fixtures/computer.rb +3 -0
  47. data/test/fixtures/computers.yml +3 -0
  48. data/test/fixtures/db_definitions/db2.sql +5 -0
  49. data/test/fixtures/db_definitions/mysql.sql +5 -0
  50. data/test/fixtures/db_definitions/postgresql.sql +5 -0
  51. data/test/fixtures/db_definitions/sqlite.sql +5 -0
  52. data/test/fixtures/db_definitions/sqlserver.sql +5 -1
  53. data/test/fixtures/fixture_database.sqlite +0 -0
  54. data/test/validations_test.rb +21 -0
  55. metadata +22 -2
@@ -1,525 +1,527 @@
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
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
+ def IRB.parse_opts() end
421
+
422
+ class << self; remove_method :start; end
423
+ def self.start(ap_path = nil, main_context = nil, workspace = nil)
424
+ $0 = File::basename(ap_path, ".rb") if ap_path
425
+
426
+ # suppress some warnings about redefined constants
427
+ old_verbose, $VERBOSE = $VERBOSE, nil
428
+ IRB.setup(ap_path)
429
+ $VERBOSE = old_verbose
430
+
431
+ if @CONF[:SCRIPT] then
432
+ irb = Irb.new(main_context, @CONF[:SCRIPT])
433
+ else
434
+ irb = Irb.new(main_context)
435
+ end
436
+
437
+ if workspace then
438
+ irb.context.workspace = workspace
439
+ end
440
+
441
+ @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
442
+ @CONF[:MAIN_CONTEXT] = irb.context
443
+
444
+ old_sigint = trap("SIGINT") do
445
+ irb.signal_handle
446
+ end
447
+
448
+ catch(:IRB_EXIT) do
449
+ irb.eval_input
450
+ end
451
+ ensure
452
+ trap("SIGINT", old_sigint)
453
+ end
454
+
455
+ class << self
456
+ alias :old_CurrentContext :CurrentContext
457
+ remove_method :CurrentContext
458
+ end
459
+ def IRB.CurrentContext
460
+ if old_CurrentContext.nil? and Breakpoint.use_drb? then
461
+ result = Object.new
462
+ def result.last_value; end
463
+ return result
464
+ else
465
+ old_CurrentContext
466
+ end
467
+ end
468
+
469
+ class Context#:nodoc:
470
+ alias :old_evaluate :evaluate
471
+ def evaluate(line, line_no)
472
+ if line.chomp == "exit" then
473
+ exit
474
+ else
475
+ old_evaluate(line, line_no)
476
+ end
477
+ end
478
+ end
479
+
480
+ class WorkSpace#:nodoc:
481
+ alias :old_evaluate :evaluate
482
+
483
+ def evaluate(*args)
484
+ if Breakpoint.use_drb? then
485
+ result = old_evaluate(*args)
486
+ if args[0] != :no_proxy and
487
+ not [true, false, nil].include?(result)
488
+ then
489
+ result.extend(DRbUndumped) rescue nil
490
+ end
491
+ return result
492
+ else
493
+ old_evaluate(*args)
494
+ end
495
+ end
496
+ end
497
+
498
+ module InputCompletor#:nodoc:
499
+ def self.eval(code, context, *more)
500
+ # Big hack, this assumes that InputCompletor
501
+ # will only call eval() when it wants code
502
+ # to be executed in the IRB context.
503
+ IRB.conf[:MAIN_CONTEXT].workspace.evaluate(:no_proxy, code, *more)
504
+ end
505
+ end
506
+ end
507
+
508
+ module DRb # :nodoc:
509
+ class DRbObject#:nodoc:
510
+ undef :inspect
511
+ undef :clone
512
+ end
513
+ end
514
+
515
+ # See Breakpoint.breakpoint
516
+ def breakpoint(id = nil, &block)
517
+ Binding.of_caller do |context|
518
+ Breakpoint.breakpoint(id, context, &block)
519
+ end
520
+ end
521
+
522
+ # See Breakpoint.assert
523
+ def assert(&block)
524
+ Binding.of_caller do |context|
525
+ Breakpoint.assert(context, &block)
526
+ end
525
527
  end