activesupport 4.0.13 → 4.1.0.beta1

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

Potentially problematic release.


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

Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +283 -508
  3. data/README.rdoc +1 -1
  4. data/lib/active_support.rb +7 -1
  5. data/lib/active_support/backtrace_cleaner.rb +5 -5
  6. data/lib/active_support/benchmarkable.rb +0 -10
  7. data/lib/active_support/cache.rb +62 -26
  8. data/lib/active_support/cache/file_store.rb +27 -22
  9. data/lib/active_support/cache/mem_cache_store.rb +2 -2
  10. data/lib/active_support/cache/memory_store.rb +1 -0
  11. data/lib/active_support/cache/strategy/local_cache.rb +3 -0
  12. data/lib/active_support/callbacks.rb +416 -245
  13. data/lib/active_support/concern.rb +13 -5
  14. data/lib/active_support/core_ext.rb +0 -1
  15. data/lib/active_support/core_ext/array.rb +0 -1
  16. data/lib/active_support/core_ext/array/access.rb +2 -0
  17. data/lib/active_support/core_ext/array/conversions.rb +2 -17
  18. data/lib/active_support/core_ext/array/grouping.rb +24 -12
  19. data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -2
  20. data/lib/active_support/core_ext/class.rb +0 -1
  21. data/lib/active_support/core_ext/class/attribute.rb +1 -2
  22. data/lib/active_support/core_ext/class/attribute_accessors.rb +5 -169
  23. data/lib/active_support/core_ext/date/calculations.rb +10 -0
  24. data/lib/active_support/core_ext/date/conversions.rb +5 -6
  25. data/lib/active_support/core_ext/date/zones.rb +2 -33
  26. data/lib/active_support/core_ext/date_and_time/calculations.rb +30 -11
  27. data/lib/active_support/core_ext/date_and_time/zones.rb +41 -0
  28. data/lib/active_support/core_ext/date_time/calculations.rb +12 -25
  29. data/lib/active_support/core_ext/date_time/conversions.rb +2 -0
  30. data/lib/active_support/core_ext/date_time/zones.rb +3 -21
  31. data/lib/active_support/core_ext/hash.rb +0 -1
  32. data/lib/active_support/core_ext/hash/conversions.rb +6 -3
  33. data/lib/active_support/core_ext/hash/deep_merge.rb +11 -22
  34. data/lib/active_support/core_ext/hash/indifferent_access.rb +1 -0
  35. data/lib/active_support/core_ext/hash/keys.rb +27 -47
  36. data/lib/active_support/core_ext/kernel/reporting.rb +2 -6
  37. data/lib/active_support/core_ext/module.rb +1 -0
  38. data/lib/active_support/core_ext/module/attribute_accessors.rb +160 -14
  39. data/lib/active_support/core_ext/module/concerning.rb +135 -0
  40. data/lib/active_support/core_ext/module/delegation.rb +14 -4
  41. data/lib/active_support/core_ext/module/deprecation.rb +0 -2
  42. data/lib/active_support/core_ext/module/introspection.rb +0 -16
  43. data/lib/active_support/core_ext/module/method_transplanting.rb +11 -0
  44. data/lib/active_support/core_ext/numeric/time.rb +8 -0
  45. data/lib/active_support/core_ext/object.rb +1 -1
  46. data/lib/active_support/core_ext/object/blank.rb +1 -1
  47. data/lib/active_support/core_ext/object/deep_dup.rb +6 -6
  48. data/lib/active_support/core_ext/object/inclusion.rb +4 -15
  49. data/lib/active_support/core_ext/object/json.rb +197 -0
  50. data/lib/active_support/core_ext/object/to_json.rb +4 -26
  51. data/lib/active_support/core_ext/object/to_param.rb +58 -1
  52. data/lib/active_support/core_ext/object/to_query.rb +7 -56
  53. data/lib/active_support/core_ext/object/try.rb +1 -1
  54. data/lib/active_support/core_ext/range/each.rb +2 -1
  55. data/lib/active_support/core_ext/string/access.rb +31 -31
  56. data/lib/active_support/core_ext/string/conversions.rb +9 -8
  57. data/lib/active_support/core_ext/string/exclude.rb +3 -3
  58. data/lib/active_support/core_ext/string/filters.rb +14 -4
  59. data/lib/active_support/core_ext/string/inflections.rb +11 -9
  60. data/lib/active_support/core_ext/string/output_safety.rb +65 -24
  61. data/lib/active_support/core_ext/string/zones.rb +1 -0
  62. data/lib/active_support/core_ext/thread.rb +4 -4
  63. data/lib/active_support/core_ext/time/calculations.rb +10 -57
  64. data/lib/active_support/core_ext/time/conversions.rb +3 -1
  65. data/lib/active_support/core_ext/time/zones.rb +2 -21
  66. data/lib/active_support/dependencies.rb +29 -13
  67. data/lib/active_support/deprecation.rb +4 -4
  68. data/lib/active_support/deprecation/behaviors.rb +3 -3
  69. data/lib/active_support/duration.rb +5 -7
  70. data/lib/active_support/file_update_checker.rb +1 -1
  71. data/lib/active_support/hash_with_indifferent_access.rb +4 -9
  72. data/lib/active_support/i18n.rb +4 -4
  73. data/lib/active_support/i18n_railtie.rb +2 -6
  74. data/lib/active_support/inflections.rb +0 -1
  75. data/lib/active_support/inflector/inflections.rb +17 -17
  76. data/lib/active_support/inflector/methods.rb +34 -17
  77. data/lib/active_support/json/decoding.rb +14 -21
  78. data/lib/active_support/json/encoding.rb +113 -285
  79. data/lib/active_support/key_generator.rb +1 -1
  80. data/lib/active_support/lazy_load_hooks.rb +1 -1
  81. data/lib/active_support/log_subscriber/test_helper.rb +1 -1
  82. data/lib/active_support/logger.rb +1 -1
  83. data/lib/active_support/message_encryptor.rb +3 -3
  84. data/lib/active_support/message_verifier.rb +6 -1
  85. data/lib/active_support/multibyte/chars.rb +1 -2
  86. data/lib/active_support/multibyte/unicode.rb +27 -39
  87. data/lib/active_support/notifications.rb +3 -3
  88. data/lib/active_support/notifications/instrumenter.rb +2 -1
  89. data/lib/active_support/number_helper.rb +20 -311
  90. data/lib/active_support/number_helper/number_converter.rb +182 -0
  91. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  92. data/lib/active_support/number_helper/number_to_delimited_converter.rb +21 -0
  93. data/lib/active_support/number_helper/number_to_human_converter.rb +66 -0
  94. data/lib/active_support/number_helper/number_to_human_size_converter.rb +58 -0
  95. data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
  96. data/lib/active_support/number_helper/number_to_phone_converter.rb +49 -0
  97. data/lib/active_support/number_helper/number_to_rounded_converter.rb +62 -0
  98. data/lib/active_support/option_merger.rb +1 -1
  99. data/lib/active_support/ordered_hash.rb +0 -8
  100. data/lib/active_support/ordered_options.rb +8 -0
  101. data/lib/active_support/per_thread_registry.rb +9 -8
  102. data/lib/active_support/subscriber.rb +26 -3
  103. data/lib/active_support/test_case.rb +9 -10
  104. data/lib/active_support/testing/assertions.rb +0 -30
  105. data/lib/active_support/testing/autorun.rb +2 -2
  106. data/lib/active_support/testing/declarative.rb +18 -8
  107. data/lib/active_support/testing/isolation.rb +13 -65
  108. data/lib/active_support/testing/setup_and_teardown.rb +17 -2
  109. data/lib/active_support/testing/tagged_logging.rb +1 -1
  110. data/lib/active_support/testing/time_helpers.rb +55 -0
  111. data/lib/active_support/time_with_zone.rb +4 -4
  112. data/lib/active_support/values/time_zone.rb +18 -15
  113. data/lib/active_support/version.rb +1 -1
  114. data/lib/active_support/xml_mini.rb +2 -4
  115. metadata +71 -61
  116. data/lib/active_support/basic_object.rb +0 -11
  117. data/lib/active_support/buffered_logger.rb +0 -21
  118. data/lib/active_support/core_ext/array/uniq_by.rb +0 -19
  119. data/lib/active_support/core_ext/hash/diff.rb +0 -14
  120. data/lib/active_support/core_ext/logger.rb +0 -67
  121. data/lib/active_support/core_ext/proc.rb +0 -17
  122. data/lib/active_support/core_ext/string/encoding.rb +0 -8
  123. data/lib/active_support/json/variable.rb +0 -18
  124. data/lib/active_support/testing/pending.rb +0 -14
@@ -14,7 +14,7 @@ The latest version of Active Support can be installed with RubyGems:
14
14
 
15
15
  Source code can be downloaded as part of the Rails project on GitHub:
16
16
 
17
- * https://github.com/rails/rails/tree/4-0-stable/activesupport
17
+ * https://github.com/rails/rails/tree/master/activesupport
18
18
 
19
19
 
20
20
  == License
@@ -39,7 +39,6 @@ module ActiveSupport
39
39
 
40
40
  eager_autoload do
41
41
  autoload :BacktraceCleaner
42
- autoload :BasicObject
43
42
  autoload :ProxyObject
44
43
  autoload :Benchmarkable
45
44
  autoload :Cache
@@ -53,6 +52,7 @@ module ActiveSupport
53
52
  autoload :MessageEncryptor
54
53
  autoload :MessageVerifier
55
54
  autoload :Multibyte
55
+ autoload :NumberHelper
56
56
  autoload :OptionMerger
57
57
  autoload :OrderedHash
58
58
  autoload :OrderedOptions
@@ -64,6 +64,12 @@ module ActiveSupport
64
64
  autoload :Rescuable
65
65
  autoload :SafeBuffer, "active_support/core_ext/string/output_safety"
66
66
  autoload :TestCase
67
+
68
+ def self.eager_load!
69
+ super
70
+
71
+ NumberHelper.eager_load!
72
+ end
67
73
  end
68
74
 
69
75
  autoload :I18n, "active_support/i18n"
@@ -13,17 +13,17 @@ module ActiveSupport
13
13
  # can focus on the rest.
14
14
  #
15
15
  # bc = BacktraceCleaner.new
16
- # bc.add_filter { |line| line.gsub(Rails.root, '') }
17
- # bc.add_silencer { |line| line =~ /mongrel|rubygems/ }
18
- # bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems
16
+ # bc.add_filter { |line| line.gsub(Rails.root, '') } # strip the Rails.root prefix
17
+ # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # skip any lines from mongrel or rubygems
18
+ # bc.clean(exception.backtrace) # perform the cleanup
19
19
  #
20
20
  # To reconfigure an existing BacktraceCleaner (like the default one in Rails)
21
21
  # and show as much data as possible, you can always call
22
22
  # <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
23
23
  # backtrace to a pristine state. If you need to reconfigure an existing
24
24
  # BacktraceCleaner so that it does not filter or modify the paths of any lines
25
- # of the backtrace, you can call BacktraceCleaner#remove_filters! These two
26
- # methods will give you a completely untouched backtrace.
25
+ # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
26
+ # These two methods will give you a completely untouched backtrace.
27
27
  #
28
28
  # Inspired by the Quiet Backtrace gem by Thoughtbot.
29
29
  class BacktraceCleaner
@@ -45,15 +45,5 @@ module ActiveSupport
45
45
  yield
46
46
  end
47
47
  end
48
-
49
- # Silence the logger during the execution of the block.
50
- def silence
51
- message = "ActiveSupport::Benchmarkable#silence is deprecated. It will be removed from Rails 4.1."
52
- ActiveSupport::Deprecation.warn message
53
- old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger
54
- yield
55
- ensure
56
- logger.level = old_logger_level if logger
57
- end
58
48
  end
59
49
  end
@@ -3,7 +3,7 @@ require 'zlib'
3
3
  require 'active_support/core_ext/array/extract_options'
4
4
  require 'active_support/core_ext/array/wrap'
5
5
  require 'active_support/core_ext/benchmark'
6
- require 'active_support/core_ext/class/attribute_accessors'
6
+ require 'active_support/core_ext/module/attribute_accessors'
7
7
  require 'active_support/core_ext/numeric/bytes'
8
8
  require 'active_support/core_ext/numeric/time'
9
9
  require 'active_support/core_ext/object/to_param'
@@ -12,10 +12,10 @@ require 'active_support/core_ext/string/inflections'
12
12
  module ActiveSupport
13
13
  # See ActiveSupport::Cache::Store for documentation.
14
14
  module Cache
15
- autoload :FileStore, 'active_support/cache/file_store'
16
- autoload :MemoryStore, 'active_support/cache/memory_store'
15
+ autoload :FileStore, 'active_support/cache/file_store'
16
+ autoload :MemoryStore, 'active_support/cache/memory_store'
17
17
  autoload :MemCacheStore, 'active_support/cache/mem_cache_store'
18
- autoload :NullStore, 'active_support/cache/null_store'
18
+ autoload :NullStore, 'active_support/cache/null_store'
19
19
 
20
20
  # These options mean something to all cache implementations. Individual cache
21
21
  # implementations may support additional options.
@@ -88,25 +88,24 @@ module ActiveSupport
88
88
  end
89
89
 
90
90
  private
91
+ def retrieve_cache_key(key)
92
+ case
93
+ when key.respond_to?(:cache_key) then key.cache_key
94
+ when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
95
+ when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
96
+ else key.to_param
97
+ end.to_s
98
+ end
91
99
 
92
- def retrieve_cache_key(key)
93
- case
94
- when key.respond_to?(:cache_key) then key.cache_key
95
- when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
96
- when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
97
- else key.to_param
98
- end.to_s
99
- end
100
-
101
- # Obtains the specified cache store class, given the name of the +store+.
102
- # Raises an error when the store class cannot be found.
103
- def retrieve_store_class(store)
104
- require "active_support/cache/#{store}"
105
- rescue LoadError => e
106
- raise "Could not find cache store adapter for #{store} (#{e})"
107
- else
108
- ActiveSupport::Cache.const_get(store.to_s.camelize)
109
- end
100
+ # Obtains the specified cache store class, given the name of the +store+.
101
+ # Raises an error when the store class cannot be found.
102
+ def retrieve_store_class(store)
103
+ require "active_support/cache/#{store}"
104
+ rescue LoadError => e
105
+ raise "Could not find cache store adapter for #{store} (#{e})"
106
+ else
107
+ ActiveSupport::Cache.const_get(store.to_s.camelize)
108
+ end
110
109
  end
111
110
 
112
111
  # An abstract cache store class. There are multiple cache store
@@ -153,7 +152,6 @@ module ActiveSupport
153
152
  # or +write+. To specify the threshold at which to compress values, set the
154
153
  # <tt>:compress_threshold</tt> option. The default threshold is 16K.
155
154
  class Store
156
-
157
155
  cattr_accessor :logger, :instance_writer => true
158
156
 
159
157
  attr_reader :silence, :options
@@ -228,7 +226,7 @@ module ActiveSupport
228
226
  #
229
227
  # Setting <tt>:race_condition_ttl</tt> is very useful in situations where
230
228
  # a cache entry is used very frequently and is under heavy load. If a
231
- # cache expires and due to heavy load seven different processes will try
229
+ # cache expires and due to heavy load several different processes will try
232
230
  # to read data natively and then they all will try to write to cache. To
233
231
  # avoid that case the first process to find an expired cache entry will
234
232
  # bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>.
@@ -352,11 +350,40 @@ module ActiveSupport
352
350
  results
353
351
  end
354
352
 
353
+ # Fetches data from the cache, using the given keys. If there is data in
354
+ # the cache with the given keys, then that data is returned. Otherwise,
355
+ # the supplied block is called for each key for which there was no data,
356
+ # and the result will be written to the cache and returned.
357
+ #
358
+ # Options are passed to the underlying cache implementation.
359
+ #
360
+ # Returns an array with the data for each of the names. For example:
361
+ #
362
+ # cache.write("bim", "bam")
363
+ # cache.fetch_multi("bim", "boom") {|key| key * 2 }
364
+ # # => ["bam", "boomboom"]
365
+ #
366
+ def fetch_multi(*names)
367
+ options = names.extract_options!
368
+ options = merged_options(options)
369
+
370
+ results = read_multi(*names, options)
371
+
372
+ names.map do |name|
373
+ results.fetch(name) do
374
+ value = yield name
375
+ write(name, value, options)
376
+ value
377
+ end
378
+ end
379
+ end
380
+
355
381
  # Writes the value to the cache, with the key.
356
382
  #
357
383
  # Options are passed to the underlying cache implementation.
358
384
  def write(name, value, options = nil)
359
385
  options = merged_options(options)
386
+
360
387
  instrument(:write, name, options) do
361
388
  entry = Entry.new(value, options)
362
389
  write_entry(namespaced_key(name, options), entry, options)
@@ -368,6 +395,7 @@ module ActiveSupport
368
395
  # Options are passed to the underlying cache implementation.
369
396
  def delete(name, options = nil)
370
397
  options = merged_options(options)
398
+
371
399
  instrument(:delete, name) do
372
400
  delete_entry(namespaced_key(name, options), options)
373
401
  end
@@ -378,9 +406,10 @@ module ActiveSupport
378
406
  # Options are passed to the underlying cache implementation.
379
407
  def exist?(name, options = nil)
380
408
  options = merged_options(options)
409
+
381
410
  instrument(:exist?, name) do
382
411
  entry = read_entry(namespaced_key(name, options), options)
383
- entry && !entry.expired?
412
+ (entry && !entry.expired?) || false
384
413
  end
385
414
  end
386
415
 
@@ -557,6 +586,7 @@ module ActiveSupport
557
586
  result = instrument(:generate, name, options) do |payload|
558
587
  yield(name)
559
588
  end
589
+
560
590
  write(name, result, options)
561
591
  result
562
592
  end
@@ -580,6 +610,7 @@ module ActiveSupport
580
610
  else
581
611
  @value = value
582
612
  end
613
+
583
614
  @created_at = Time.now.to_f
584
615
  @expires_in = options[:expires_in]
585
616
  @expires_in = @expires_in.to_f if @expires_in
@@ -593,7 +624,7 @@ module ActiveSupport
593
624
  # Check if the entry is expired. The +expires_in+ parameter can override
594
625
  # the value set when the entry was created.
595
626
  def expired?
596
- convert_version_4beta1_entry! if defined?(@v)
627
+ convert_version_4beta1_entry! if defined?(@value)
597
628
  @expires_in && @created_at + @expires_in <= Time.now.to_f
598
629
  end
599
630
 
@@ -630,6 +661,7 @@ module ActiveSupport
630
661
  # serialize entries to protect against accidental cache modifications.
631
662
  def dup_value!
632
663
  convert_version_4beta1_entry! if defined?(@v)
664
+
633
665
  if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
634
666
  if @value.is_a?(String)
635
667
  @value = @value.dup
@@ -644,8 +676,10 @@ module ActiveSupport
644
676
  if value && options[:compress]
645
677
  compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
646
678
  serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize
679
+
647
680
  return true if serialized_value_size >= compress_threshold
648
681
  end
682
+
649
683
  false
650
684
  end
651
685
 
@@ -668,10 +702,12 @@ module ActiveSupport
668
702
  @value = @v
669
703
  remove_instance_variable(:@v)
670
704
  end
705
+
671
706
  if defined?(@c)
672
707
  @compressed = @c
673
708
  remove_instance_variable(:@c)
674
709
  end
710
+
675
711
  if defined?(@x) && @x
676
712
  @created_at ||= Time.now.to_f
677
713
  @expires_in = @x - @created_at
@@ -22,11 +22,15 @@ module ActiveSupport
22
22
  extend Strategy::LocalCache
23
23
  end
24
24
 
25
+ # Deletes all items from the cache. In this case it deletes all the entries in the specified
26
+ # file store directory except for .gitkeep. Be careful which directory is specified in your
27
+ # config file when using +FileStore+ because everything in that directory will be deleted.
25
28
  def clear(options = nil)
26
29
  root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)}
27
30
  FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
28
31
  end
29
32
 
33
+ # Preemptively iterates through all stored keys and removes the ones which have expired.
30
34
  def cleanup(options = nil)
31
35
  options = merged_options(options)
32
36
  search_dir(cache_path) do |fname|
@@ -36,32 +40,16 @@ module ActiveSupport
36
40
  end
37
41
  end
38
42
 
43
+ # Increments an already existing integer value that is stored in the cache.
44
+ # If the key is not found nothing is done.
39
45
  def increment(name, amount = 1, options = nil)
40
- file_name = key_file_path(namespaced_key(name, options))
41
- lock_file(file_name) do
42
- options = merged_options(options)
43
- if num = read(name, options)
44
- num = num.to_i + amount
45
- write(name, num, options)
46
- num
47
- else
48
- nil
49
- end
50
- end
46
+ modify_value(name, amount, options)
51
47
  end
52
48
 
49
+ # Decrements an already existing integer value that is stored in the cache.
50
+ # If the key is not found nothing is done.
53
51
  def decrement(name, amount = 1, options = nil)
54
- file_name = key_file_path(namespaced_key(name, options))
55
- lock_file(file_name) do
56
- options = merged_options(options)
57
- if num = read(name, options)
58
- num = num.to_i - amount
59
- write(name, num, options)
60
- num
61
- else
62
- nil
63
- end
64
- end
52
+ modify_value(name, -amount, options)
65
53
  end
66
54
 
67
55
  def delete_matched(matcher, options = nil)
@@ -89,6 +77,7 @@ module ActiveSupport
89
77
 
90
78
  def write_entry(key, entry, options)
91
79
  file_name = key_file_path(key)
80
+ return false if options[:unless_exist] && File.exist?(file_name)
92
81
  ensure_cache_path(File.dirname(file_name))
93
82
  File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)}
94
83
  true
@@ -175,6 +164,22 @@ module ActiveSupport
175
164
  end
176
165
  end
177
166
  end
167
+
168
+ # Modifies the amount of an already existing integer value that is stored in the cache.
169
+ # If the key is not found nothing is done.
170
+ def modify_value(name, amount, options)
171
+ file_name = key_file_path(namespaced_key(name, options))
172
+
173
+ lock_file(file_name) do
174
+ options = merged_options(options)
175
+
176
+ if num = read(name, options)
177
+ num = num.to_i + amount
178
+ write(name, num, options)
179
+ num
180
+ end
181
+ end
182
+ end
178
183
  end
179
184
  end
180
185
  end
@@ -87,7 +87,7 @@ module ActiveSupport
87
87
  instrument(:increment, name, :amount => amount) do
88
88
  @data.incr(escape_key(namespaced_key(name, options)), amount)
89
89
  end
90
- rescue Dalli::DalliError => e
90
+ rescue Dalli::DalliError
91
91
  logger.error("DalliError (#{e}): #{e.message}") if logger
92
92
  nil
93
93
  end
@@ -101,7 +101,7 @@ module ActiveSupport
101
101
  instrument(:decrement, name, :amount => amount) do
102
102
  @data.decr(escape_key(namespaced_key(name, options)), amount)
103
103
  end
104
- rescue Dalli::DalliError => e
104
+ rescue Dalli::DalliError
105
105
  logger.error("DalliError (#{e}): #{e.message}") if logger
106
106
  nil
107
107
  end
@@ -36,6 +36,7 @@ module ActiveSupport
36
36
  end
37
37
  end
38
38
 
39
+ # Preemptively iterates through all stored keys and removes the ones which have expired.
39
40
  def cleanup(options = nil)
40
41
  options = merged_options(options)
41
42
  instrument(:cleanup, :size => @data.size) do
@@ -23,6 +23,9 @@ module ActiveSupport
23
23
  def set_cache_for(local_cache_key, value)
24
24
  @registry[local_cache_key] = value
25
25
  end
26
+
27
+ def self.set_cache_for(l, v); instance.set_cache_for l, v; end
28
+ def self.cache_for(l); instance.cache_for l; end
26
29
  end
27
30
 
28
31
  # Simple memory backed cache. This cache is not thread safe and is intended only
@@ -1,9 +1,10 @@
1
- require 'thread_safe'
2
1
  require 'active_support/concern'
3
2
  require 'active_support/descendants_tracker'
3
+ require 'active_support/core_ext/array/extract_options'
4
4
  require 'active_support/core_ext/class/attribute'
5
5
  require 'active_support/core_ext/kernel/reporting'
6
6
  require 'active_support/core_ext/kernel/singleton_class'
7
+ require 'thread'
7
8
 
8
9
  module ActiveSupport
9
10
  # Callbacks are code hooks that are run at key points in an object's life cycle.
@@ -76,171 +77,333 @@ module ActiveSupport
76
77
  # save
77
78
  # end
78
79
  def run_callbacks(kind, &block)
79
- runner_name = self.class.__define_callbacks(kind, self)
80
- send(runner_name, &block)
80
+ cbs = send("_#{kind}_callbacks")
81
+ if cbs.empty?
82
+ yield if block_given?
83
+ else
84
+ runner = cbs.compile
85
+ e = Filters::Environment.new(self, false, nil, block)
86
+ runner.call(e).value
87
+ end
81
88
  end
82
89
 
83
90
  private
84
91
 
85
- # A hook invoked everytime a before callback is halted.
92
+ # A hook invoked every time a before callback is halted.
86
93
  # This can be overridden in AS::Callback implementors in order
87
94
  # to provide better debugging/logging.
88
95
  def halted_callback_hook(filter)
89
96
  end
90
97
 
91
- class Callback #:nodoc:#
92
- @@_callback_sequence = 0
93
-
94
- attr_accessor :chain, :filter, :kind, :options, :klass, :raw_filter
98
+ module Conditionals # :nodoc:
99
+ class Value
100
+ def initialize(&block)
101
+ @block = block
102
+ end
103
+ def call(target, value); @block.call(value); end
104
+ end
105
+ end
95
106
 
96
- def initialize(chain, filter, kind, options, klass)
97
- @chain, @kind, @klass = chain, kind, klass
98
- deprecate_per_key_option(options)
99
- normalize_options!(options)
107
+ module Filters
108
+ Environment = Struct.new(:target, :halted, :value, :run_block)
100
109
 
101
- @raw_filter, @options = filter, options
102
- @filter = _compile_filter(filter)
103
- recompile_options!
110
+ class End
111
+ def call(env)
112
+ block = env.run_block
113
+ env.value = !env.halted && (!block || block.call)
114
+ env
115
+ end
104
116
  end
117
+ ENDING = End.new
105
118
 
106
- def deprecate_per_key_option(options)
107
- if options[:per_key]
108
- raise NotImplementedError, ":per_key option is no longer supported. Use generic :if and :unless options instead."
119
+ class Before
120
+ def self.build(next_callback, user_callback, user_conditions, chain_config, filter)
121
+ halted_lambda = chain_config[:terminator]
122
+
123
+ if chain_config.key?(:terminator) && user_conditions.any?
124
+ halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
125
+ elsif chain_config.key? :terminator
126
+ halting(next_callback, user_callback, halted_lambda, filter)
127
+ elsif user_conditions.any?
128
+ conditional(next_callback, user_callback, user_conditions)
129
+ else
130
+ simple next_callback, user_callback
131
+ end
109
132
  end
110
- end
111
133
 
112
- def clone(chain, klass)
113
- obj = super()
114
- obj.chain = chain
115
- obj.klass = klass
116
- obj.options = @options.dup
117
- obj.options[:if] = @options[:if].dup
118
- obj.options[:unless] = @options[:unless].dup
119
- obj
120
- end
134
+ private
135
+
136
+ def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
137
+ lambda { |env|
138
+ target = env.target
139
+ value = env.value
140
+ halted = env.halted
121
141
 
122
- def normalize_options!(options)
123
- options[:if] = Array(options[:if])
124
- options[:unless] = Array(options[:unless])
142
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
143
+ result = user_callback.call target, value
144
+ env.halted = halted_lambda.call(target, result)
145
+ if env.halted
146
+ target.send :halted_callback_hook, filter
147
+ end
148
+ end
149
+ next_callback.call env
150
+ }
151
+ end
152
+
153
+ def self.halting(next_callback, user_callback, halted_lambda, filter)
154
+ lambda { |env|
155
+ target = env.target
156
+ value = env.value
157
+ halted = env.halted
158
+
159
+ unless halted
160
+ result = user_callback.call target, value
161
+ env.halted = halted_lambda.call(target, result)
162
+ if env.halted
163
+ target.send :halted_callback_hook, filter
164
+ end
165
+ end
166
+ next_callback.call env
167
+ }
168
+ end
169
+
170
+ def self.conditional(next_callback, user_callback, user_conditions)
171
+ lambda { |env|
172
+ target = env.target
173
+ value = env.value
174
+
175
+ if user_conditions.all? { |c| c.call(target, value) }
176
+ user_callback.call target, value
177
+ end
178
+ next_callback.call env
179
+ }
180
+ end
181
+
182
+ def self.simple(next_callback, user_callback)
183
+ lambda { |env|
184
+ user_callback.call env.target, env.value
185
+ next_callback.call env
186
+ }
187
+ end
125
188
  end
126
189
 
127
- def name
128
- chain.name
190
+ class After
191
+ def self.build(next_callback, user_callback, user_conditions, chain_config)
192
+ if chain_config[:skip_after_callbacks_if_terminated]
193
+ if chain_config.key?(:terminator) && user_conditions.any?
194
+ halting_and_conditional(next_callback, user_callback, user_conditions)
195
+ elsif chain_config.key?(:terminator)
196
+ halting(next_callback, user_callback)
197
+ elsif user_conditions.any?
198
+ conditional next_callback, user_callback, user_conditions
199
+ else
200
+ simple next_callback, user_callback
201
+ end
202
+ else
203
+ if user_conditions.any?
204
+ conditional next_callback, user_callback, user_conditions
205
+ else
206
+ simple next_callback, user_callback
207
+ end
208
+ end
209
+ end
210
+
211
+ private
212
+
213
+ def self.halting_and_conditional(next_callback, user_callback, user_conditions)
214
+ lambda { |env|
215
+ env = next_callback.call env
216
+ target = env.target
217
+ value = env.value
218
+ halted = env.halted
219
+
220
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
221
+ user_callback.call target, value
222
+ end
223
+ env
224
+ }
225
+ end
226
+
227
+ def self.halting(next_callback, user_callback)
228
+ lambda { |env|
229
+ env = next_callback.call env
230
+ unless env.halted
231
+ user_callback.call env.target, env.value
232
+ end
233
+ env
234
+ }
235
+ end
236
+
237
+ def self.conditional(next_callback, user_callback, user_conditions)
238
+ lambda { |env|
239
+ env = next_callback.call env
240
+ target = env.target
241
+ value = env.value
242
+
243
+ if user_conditions.all? { |c| c.call(target, value) }
244
+ user_callback.call target, value
245
+ end
246
+ env
247
+ }
248
+ end
249
+
250
+ def self.simple(next_callback, user_callback)
251
+ lambda { |env|
252
+ env = next_callback.call env
253
+ user_callback.call env.target, env.value
254
+ env
255
+ }
256
+ end
129
257
  end
130
258
 
131
- def next_id
132
- @@_callback_sequence += 1
259
+ class Around
260
+ def self.build(next_callback, user_callback, user_conditions, chain_config)
261
+ if chain_config.key?(:terminator) && user_conditions.any?
262
+ halting_and_conditional(next_callback, user_callback, user_conditions)
263
+ elsif chain_config.key? :terminator
264
+ halting(next_callback, user_callback)
265
+ elsif user_conditions.any?
266
+ conditional(next_callback, user_callback, user_conditions)
267
+ else
268
+ simple(next_callback, user_callback)
269
+ end
270
+ end
271
+
272
+ private
273
+
274
+ def self.halting_and_conditional(next_callback, user_callback, user_conditions)
275
+ lambda { |env|
276
+ target = env.target
277
+ value = env.value
278
+ halted = env.halted
279
+
280
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
281
+ user_callback.call(target, value) {
282
+ env = next_callback.call env
283
+ env.value
284
+ }
285
+ env
286
+ else
287
+ next_callback.call env
288
+ end
289
+ }
290
+ end
291
+
292
+ def self.halting(next_callback, user_callback)
293
+ lambda { |env|
294
+ target = env.target
295
+ value = env.value
296
+
297
+ unless env.halted
298
+ user_callback.call(target, value) {
299
+ env = next_callback.call env
300
+ env.value
301
+ }
302
+ env
303
+ else
304
+ next_callback.call env
305
+ end
306
+ }
307
+ end
308
+
309
+ def self.conditional(next_callback, user_callback, user_conditions)
310
+ lambda { |env|
311
+ target = env.target
312
+ value = env.value
313
+
314
+ if user_conditions.all? { |c| c.call(target, value) }
315
+ user_callback.call(target, value) {
316
+ env = next_callback.call env
317
+ env.value
318
+ }
319
+ env
320
+ else
321
+ next_callback.call env
322
+ end
323
+ }
324
+ end
325
+
326
+ def self.simple(next_callback, user_callback)
327
+ lambda { |env|
328
+ user_callback.call(env.target, env.value) {
329
+ env = next_callback.call env
330
+ env.value
331
+ }
332
+ env
333
+ }
334
+ end
133
335
  end
336
+ end
134
337
 
135
- def matches?(_kind, _filter)
136
- @kind == _kind && @filter == _filter
338
+ class Callback #:nodoc:#
339
+ def self.build(chain, filter, kind, options)
340
+ new chain.name, filter, kind, options, chain.config
137
341
  end
138
342
 
139
- def duplicates?(other)
140
- matches?(other.kind, other.filter)
343
+ attr_accessor :kind, :name
344
+ attr_reader :chain_config
345
+
346
+ def initialize(name, filter, kind, options, chain_config)
347
+ @chain_config = chain_config
348
+ @name = name
349
+ @kind = kind
350
+ @filter = filter
351
+ @key = compute_identifier filter
352
+ @if = Array(options[:if])
353
+ @unless = Array(options[:unless])
141
354
  end
142
355
 
143
- def _update_filter(filter_options, new_options)
144
- filter_options[:if].concat(Array(new_options[:unless])) if new_options.key?(:unless)
145
- filter_options[:unless].concat(Array(new_options[:if])) if new_options.key?(:if)
356
+ def filter; @key; end
357
+ def raw_filter; @filter; end
358
+
359
+ def merge(chain, new_options)
360
+ options = {
361
+ :if => @if.dup,
362
+ :unless => @unless.dup
363
+ }
364
+
365
+ options[:if].concat Array(new_options.fetch(:unless, []))
366
+ options[:unless].concat Array(new_options.fetch(:if, []))
367
+
368
+ self.class.build chain, @filter, @kind, options
146
369
  end
147
370
 
148
- def recompile!(_options)
149
- deprecate_per_key_option(_options)
150
- _update_filter(self.options, _options)
371
+ def matches?(_kind, _filter)
372
+ @kind == _kind && filter == _filter
373
+ end
151
374
 
152
- recompile_options!
375
+ def duplicates?(other)
376
+ case @filter
377
+ when Symbol, String
378
+ matches?(other.kind, other.filter)
379
+ else
380
+ false
381
+ end
153
382
  end
154
383
 
155
384
  # Wraps code with filter
156
- def apply(code)
157
- case @kind
385
+ def apply(next_callback)
386
+ user_conditions = conditions_lambdas
387
+ user_callback = make_lambda @filter
388
+
389
+ case kind
158
390
  when :before
159
- <<-RUBY_EVAL
160
- if !halted && #{@compiled_options}
161
- # This double assignment is to prevent warnings in 1.9.3 as
162
- # the `result` variable is not always used except if the
163
- # terminator code refers to it.
164
- result = result = #{@filter}
165
- halted = (#{chain.config[:terminator]})
166
- if halted
167
- halted_callback_hook(#{@raw_filter.inspect.inspect})
168
- end
169
- end
170
- #{code}
171
- RUBY_EVAL
391
+ Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter)
172
392
  when :after
173
- <<-RUBY_EVAL
174
- #{code}
175
- if #{!chain.config[:skip_after_callbacks_if_terminated] || "!halted"} && #{@compiled_options}
176
- #{@filter}
177
- end
178
- RUBY_EVAL
393
+ Filters::After.build(next_callback, user_callback, user_conditions, chain_config)
179
394
  when :around
180
- name = define_conditional_callback
181
- <<-RUBY_EVAL
182
- #{name}(halted) do
183
- #{code}
184
- value
185
- end
186
- RUBY_EVAL
395
+ Filters::Around.build(next_callback, user_callback, user_conditions, chain_config)
187
396
  end
188
397
  end
189
398
 
190
399
  private
191
400
 
192
- # Compile around filters with conditions into proxy methods
193
- # that contain the conditions.
194
- #
195
- # For `set_callback :save, :around, :filter_name, if: :condition':
196
- #
197
- # def _conditional_callback_save_17
198
- # if condition
199
- # filter_name do
200
- # yield self
201
- # end
202
- # else
203
- # yield self
204
- # end
205
- # end
206
- def define_conditional_callback
207
- name = "_conditional_callback_#{@kind}_#{next_id}"
208
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
209
- def #{name}(halted)
210
- if #{@compiled_options} && !halted
211
- #{@filter} do
212
- yield self
213
- end
214
- else
215
- yield self
216
- end
217
- end
218
- RUBY_EVAL
219
- name
220
- end
221
-
222
- # Options support the same options as filters themselves (and support
223
- # symbols, string, procs, and objects), so compile a conditional
224
- # expression based on the options.
225
- def recompile_options!
226
- conditions = ["true"]
227
-
228
- unless options[:if].empty?
229
- conditions << Array(_compile_filter(options[:if]))
230
- end
231
-
232
- unless options[:unless].empty?
233
- conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
234
- end
235
-
236
- @compiled_options = conditions.flatten.join(" && ")
401
+ def invert_lambda(l)
402
+ lambda { |*args, &blk| !l.call(*args, &blk) }
237
403
  end
238
404
 
239
405
  # Filters support:
240
406
  #
241
- # Arrays:: Used in conditions. This is used to specify
242
- # multiple conditions. Used internally to
243
- # merge conditions from skip_* filters.
244
407
  # Symbols:: A method to call.
245
408
  # Strings:: Some content to evaluate.
246
409
  # Procs:: A proc to call with the object.
@@ -249,87 +412,106 @@ module ActiveSupport
249
412
  # All of these objects are compiled into methods and handled
250
413
  # the same after this point:
251
414
  #
252
- # Arrays:: Merged together into a single filter.
253
415
  # Symbols:: Already methods.
254
- # Strings:: class_eval'ed into methods.
255
- # Procs:: define_method'ed into methods.
416
+ # Strings:: class_eval'd into methods.
417
+ # Procs:: using define_method compiled into methods.
256
418
  # Objects::
257
419
  # a method is created that calls the before_foo method
258
420
  # on the object.
259
- def _compile_filter(filter)
421
+ def make_lambda(filter)
260
422
  case filter
261
- when Array
262
- filter.map {|f| _compile_filter(f)}
263
423
  when Symbol
264
- filter
424
+ lambda { |target, _, &blk| target.send filter, &blk }
265
425
  when String
266
- "(#{filter})"
267
- when Proc
268
- method_name = "_callback_#{@kind}_#{next_id}"
269
- @klass.send(:define_method, method_name, &filter)
270
- return method_name if filter.arity <= 0
426
+ l = eval "lambda { |value| #{filter} }"
427
+ lambda { |target, value| target.instance_exec(value, &l) }
428
+ when Conditionals::Value then filter
429
+ when ::Proc
430
+ if filter.arity > 1
431
+ return lambda { |target, _, &block|
432
+ raise ArgumentError unless block
433
+ target.instance_exec(target, block, &filter)
434
+ }
435
+ end
271
436
 
272
- method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
437
+ if filter.arity <= 0
438
+ lambda { |target, _| target.instance_exec(&filter) }
439
+ else
440
+ lambda { |target, _| target.instance_exec(target, &filter) }
441
+ end
273
442
  else
274
- method_name = "_callback_#{@kind}_#{next_id}"
275
- @klass.send(:define_method, "#{method_name}_object") { filter }
443
+ scopes = Array(chain_config[:scope])
444
+ method_to_call = scopes.map{ |s| public_send(s) }.join("_")
276
445
 
277
- _normalize_legacy_filter(kind, filter)
278
- scopes = Array(chain.config[:scope])
279
- method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_")
280
-
281
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
282
- def #{method_name}(&blk)
283
- #{method_name}_object.send(:#{method_to_call}, self, &blk)
284
- end
285
- RUBY_EVAL
286
-
287
- method_name
446
+ lambda { |target, _, &blk|
447
+ filter.public_send method_to_call, target, &blk
448
+ }
288
449
  end
289
450
  end
290
451
 
291
- def _normalize_legacy_filter(kind, filter)
292
- if !filter.respond_to?(kind) && filter.respond_to?(:filter)
293
- message = "Filter object with #filter method is deprecated. Define method corresponding " \
294
- "to filter type (#before, #after or #around)."
295
- ActiveSupport::Deprecation.warn message
296
- filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
297
- def #{kind}(context, &block) filter(context, &block) end
298
- RUBY_EVAL
299
- elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around && !filter.respond_to?(:around)
300
- message = "Filter object with #before and #after methods is deprecated. Define #around method instead."
301
- ActiveSupport::Deprecation.warn message
302
- def filter.around(context)
303
- should_continue = before(context)
304
- yield if should_continue
305
- after(context)
306
- end
452
+ def compute_identifier(filter)
453
+ case filter
454
+ when String, ::Proc
455
+ filter.object_id
456
+ else
457
+ filter
307
458
  end
308
459
  end
460
+
461
+ def conditions_lambdas
462
+ @if.map { |c| make_lambda c } +
463
+ @unless.map { |c| invert_lambda make_lambda c }
464
+ end
309
465
  end
310
466
 
311
467
  # An Array with a compile method.
312
- class CallbackChain < Array #:nodoc:#
468
+ class CallbackChain #:nodoc:#
469
+ include Enumerable
470
+
313
471
  attr_reader :name, :config
314
472
 
315
473
  def initialize(name, config)
316
474
  @name = name
317
475
  @config = {
318
- :terminator => "false",
319
476
  :scope => [ :kind ]
320
477
  }.merge!(config)
478
+ @chain = []
479
+ @callbacks = nil
480
+ @mutex = Mutex.new
481
+ end
482
+
483
+ def each(&block); @chain.each(&block); end
484
+ def index(o); @chain.index(o); end
485
+ def empty?; @chain.empty?; end
486
+
487
+ def insert(index, o)
488
+ @callbacks = nil
489
+ @chain.insert(index, o)
490
+ end
491
+
492
+ def delete(o)
493
+ @callbacks = nil
494
+ @chain.delete(o)
495
+ end
496
+
497
+ def clear
498
+ @callbacks = nil
499
+ @chain.clear
500
+ self
501
+ end
502
+
503
+ def initialize_copy(other)
504
+ @callbacks = nil
505
+ @chain = other.chain.dup
506
+ @mutex = Mutex.new
321
507
  end
322
508
 
323
509
  def compile
324
- method = ["value = nil", "halted = false"]
325
- callbacks = "value = !halted && (!block_given? || yield)"
326
- reverse_each do |callback|
327
- callbacks = callback.apply(callbacks)
510
+ @callbacks || @mutex.synchronize do
511
+ @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback|
512
+ callback.apply chain
513
+ end
328
514
  end
329
- method << callbacks
330
-
331
- method << "value"
332
- method.join("\n")
333
515
  end
334
516
 
335
517
  def append(*callbacks)
@@ -340,69 +522,43 @@ module ActiveSupport
340
522
  callbacks.each { |c| prepend_one(c) }
341
523
  end
342
524
 
525
+ protected
526
+ def chain; @chain; end
527
+
343
528
  private
344
529
 
345
530
  def append_one(callback)
531
+ @callbacks = nil
346
532
  remove_duplicates(callback)
347
- push(callback)
533
+ @chain.push(callback)
348
534
  end
349
535
 
350
536
  def prepend_one(callback)
537
+ @callbacks = nil
351
538
  remove_duplicates(callback)
352
- unshift(callback)
539
+ @chain.unshift(callback)
353
540
  end
354
541
 
355
542
  def remove_duplicates(callback)
356
- delete_if { |c| callback.duplicates?(c) }
543
+ @callbacks = nil
544
+ @chain.delete_if { |c| callback.duplicates?(c) }
357
545
  end
358
-
359
546
  end
360
547
 
361
548
  module ClassMethods
362
-
363
- # This method defines callback chain method for the given kind
364
- # if it was not yet defined.
365
- # This generated method plays caching role.
366
- def __define_callbacks(kind, object) #:nodoc:
367
- name = __callback_runner_name(kind)
368
- unless object.respond_to?(name, true)
369
- str = object.send("_#{kind}_callbacks").compile
370
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
371
- def #{name}() #{str} end
372
- protected :#{name}
373
- RUBY_EVAL
374
- end
375
- name
376
- end
377
-
378
- def __reset_runner(symbol)
379
- name = __callback_runner_name(symbol)
380
- undef_method(name) if method_defined?(name)
381
- end
382
-
383
- def __callback_runner_name_cache
384
- @__callback_runner_name_cache ||= ThreadSafe::Cache.new {|cache, kind| cache[kind] = __generate_callback_runner_name(kind) }
385
- end
386
-
387
- def __generate_callback_runner_name(kind)
388
- "_run__#{self.name.hash.abs}__#{kind}__callbacks"
389
- end
390
-
391
- def __callback_runner_name(kind)
392
- __callback_runner_name_cache[kind]
549
+ def normalize_callback_params(filters, block) # :nodoc:
550
+ type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
551
+ options = filters.extract_options!
552
+ filters.unshift(block) if block
553
+ [type, filters, options.dup]
393
554
  end
394
555
 
395
556
  # This is used internally to append, prepend and skip callbacks to the
396
557
  # CallbackChain.
397
- def __update_callbacks(name, filters = [], block = nil) #:nodoc:
398
- type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
399
- options = filters.last.is_a?(Hash) ? filters.pop : {}
400
- filters.unshift(block) if block
401
-
558
+ def __update_callbacks(name) #:nodoc:
402
559
  ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
403
- chain = target.send("_#{name}_callbacks")
404
- yield target, chain.dup, type, filters, options
405
- target.__reset_runner(name)
560
+ chain = target.get_callbacks name
561
+ yield target, chain.dup
406
562
  end
407
563
  end
408
564
 
@@ -442,16 +598,15 @@ module ActiveSupport
442
598
  # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
443
599
  # existing chain rather than appended.
444
600
  def set_callback(name, *filter_list, &block)
445
- mapped = nil
446
-
447
- __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
448
- mapped ||= filters.map do |filter|
449
- Callback.new(chain, filter, type, options.dup, self)
450
- end
601
+ type, filters, options = normalize_callback_params(filter_list, block)
602
+ self_chain = get_callbacks name
603
+ mapped = filters.map do |filter|
604
+ Callback.build(self_chain, filter, type, options)
605
+ end
451
606
 
607
+ __update_callbacks(name) do |target, chain|
452
608
  options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
453
-
454
- target.send("_#{name}_callbacks=", chain)
609
+ target.set_callbacks name, chain
455
610
  end
456
611
  end
457
612
 
@@ -463,36 +618,34 @@ module ActiveSupport
463
618
  # skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
464
619
  # end
465
620
  def skip_callback(name, *filter_list, &block)
466
- __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
621
+ type, filters, options = normalize_callback_params(filter_list, block)
622
+
623
+ __update_callbacks(name) do |target, chain|
467
624
  filters.each do |filter|
468
625
  filter = chain.find {|c| c.matches?(type, filter) }
469
626
 
470
627
  if filter && options.any?
471
- new_filter = filter.clone(chain, self)
628
+ new_filter = filter.merge(chain, options)
472
629
  chain.insert(chain.index(filter), new_filter)
473
- new_filter.recompile!(options)
474
630
  end
475
631
 
476
632
  chain.delete(filter)
477
633
  end
478
- target.send("_#{name}_callbacks=", chain)
634
+ target.set_callbacks name, chain
479
635
  end
480
636
  end
481
637
 
482
638
  # Remove all set callbacks for the given event.
483
- def reset_callbacks(symbol)
484
- callbacks = send("_#{symbol}_callbacks")
639
+ def reset_callbacks(name)
640
+ callbacks = get_callbacks name
485
641
 
486
642
  ActiveSupport::DescendantsTracker.descendants(self).each do |target|
487
- chain = target.send("_#{symbol}_callbacks").dup
643
+ chain = target.get_callbacks(name).dup
488
644
  callbacks.each { |c| chain.delete(c) }
489
- target.send("_#{symbol}_callbacks=", chain)
490
- target.__reset_runner(symbol)
645
+ target.set_callbacks name, chain
491
646
  end
492
647
 
493
- self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
494
-
495
- __reset_runner(symbol)
648
+ self.set_callbacks name, callbacks.dup.clear
496
649
  end
497
650
 
498
651
  # Define sets of events in the object life cycle that support callbacks.
@@ -504,10 +657,11 @@ module ActiveSupport
504
657
  #
505
658
  # * <tt>:terminator</tt> - Determines when a before filter will halt the
506
659
  # callback chain, preventing following callbacks from being called and
507
- # the event from being triggered. This is a string to be eval'ed. The
508
- # result of the callback is available in the +result+ variable.
660
+ # the event from being triggered. This should be a lambda to be executed.
661
+ # The current object and the return result of the callback will be called
662
+ # with the lambda.
509
663
  #
510
- # define_callbacks :validate, terminator: 'result == false'
664
+ # define_callbacks :validate, terminator: ->(target, result) { result == false }
511
665
  #
512
666
  # In this example, if any before validate callbacks returns +false+,
513
667
  # other callbacks are not executed. Defaults to +false+, meaning no value
@@ -562,13 +716,30 @@ module ActiveSupport
562
716
  # define_callbacks :save, scope: [:name]
563
717
  #
564
718
  # would call <tt>Audit#save</tt>.
565
- def define_callbacks(*callbacks)
566
- config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
567
- callbacks.each do |callback|
568
- class_attribute "_#{callback}_callbacks"
569
- send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
719
+ def define_callbacks(*names)
720
+ options = names.extract_options!
721
+ if options.key?(:terminator) && String === options[:terminator]
722
+ ActiveSupport::Deprecation.warn "String based terminators are deprecated, please use a lambda"
723
+ value = options[:terminator]
724
+ line = class_eval "lambda { |result| #{value} }", __FILE__, __LINE__
725
+ options[:terminator] = lambda { |target, result| target.instance_exec(result, &line) }
726
+ end
727
+
728
+ names.each do |name|
729
+ class_attribute "_#{name}_callbacks"
730
+ set_callbacks name, CallbackChain.new(name, options)
570
731
  end
571
732
  end
733
+
734
+ protected
735
+
736
+ def get_callbacks(name)
737
+ send "_#{name}_callbacks"
738
+ end
739
+
740
+ def set_callbacks(name, callbacks)
741
+ send "_#{name}_callbacks=", callbacks
742
+ end
572
743
  end
573
744
  end
574
745
  end