activesupport 4.0.0.beta1 → 4.0.0.rc1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/README.rdoc +2 -2
  4. data/lib/active_support/cache.rb +62 -49
  5. data/lib/active_support/cache/file_store.rb +2 -2
  6. data/lib/active_support/cache/strategy/local_cache.rb +48 -37
  7. data/lib/active_support/callbacks.rb +27 -8
  8. data/lib/active_support/core_ext.rb +2 -2
  9. data/lib/active_support/core_ext/array/conversions.rb +5 -3
  10. data/lib/active_support/core_ext/array/uniq_by.rb +2 -2
  11. data/lib/active_support/core_ext/benchmark.rb +7 -0
  12. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -0
  13. data/lib/active_support/core_ext/class/attribute.rb +31 -27
  14. data/lib/active_support/core_ext/class/attribute_accessors.rb +2 -2
  15. data/lib/active_support/core_ext/date_and_time/calculations.rb +6 -6
  16. data/lib/active_support/core_ext/module/delegation.rb +50 -24
  17. data/lib/active_support/core_ext/module/deprecation.rb +2 -2
  18. data/lib/active_support/core_ext/string.rb +0 -1
  19. data/lib/active_support/core_ext/string/conversions.rb +6 -8
  20. data/lib/active_support/core_ext/string/filters.rb +1 -1
  21. data/lib/active_support/core_ext/string/indent.rb +1 -1
  22. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  23. data/lib/active_support/core_ext/string/output_safety.rb +2 -2
  24. data/lib/active_support/hash_with_indifferent_access.rb +2 -2
  25. data/lib/active_support/key_generator.rb +1 -1
  26. data/lib/active_support/log_subscriber.rb +9 -46
  27. data/lib/active_support/message_encryptor.rb +9 -9
  28. data/lib/active_support/message_verifier.rb +3 -3
  29. data/lib/active_support/notifications.rb +22 -1
  30. data/lib/active_support/notifications/instrumenter.rb +2 -2
  31. data/lib/active_support/number_helper.rb +3 -2
  32. data/lib/active_support/per_thread_registry.rb +52 -0
  33. data/lib/active_support/subscriber.rb +93 -0
  34. data/lib/active_support/testing/constant_lookup.rb +2 -0
  35. data/lib/active_support/time_with_zone.rb +2 -0
  36. data/lib/active_support/values/time_zone.rb +5 -3
  37. data/lib/active_support/version.rb +7 -6
  38. data/lib/active_support/xml_mini/jdom.rb +6 -0
  39. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  40. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  41. metadata +14 -7
  42. data/lib/active_support/core_ext/string/xchar.rb +0 -18
@@ -61,6 +61,8 @@ module ActiveSupport
61
61
  extend ActiveSupport::DescendantsTracker
62
62
  end
63
63
 
64
+ CALLBACK_FILTER_TYPES = [:before, :after, :around]
65
+
64
66
  # Runs the callbacks for the given event.
65
67
  #
66
68
  # Calls the before and around callbacks in the order they were set, yields
@@ -131,7 +133,13 @@ module ActiveSupport
131
133
  end
132
134
 
133
135
  def matches?(_kind, _filter)
134
- @kind == _kind && @filter == _filter
136
+ if @_is_object_filter
137
+ _filter_matches = @filter.to_s.start_with?(_method_name_for_object_filter(_kind, _filter, false))
138
+ else
139
+ _filter_matches = (@filter == _filter)
140
+ end
141
+
142
+ @kind == _kind && _filter_matches
135
143
  end
136
144
 
137
145
  def duplicates?(other)
@@ -234,6 +242,16 @@ module ActiveSupport
234
242
  @compiled_options = conditions.flatten.join(" && ")
235
243
  end
236
244
 
245
+ def _method_name_for_object_filter(kind, filter, append_next_id = true)
246
+ class_name = filter.kind_of?(Class) ? filter.to_s : filter.class.to_s
247
+ class_name.gsub!(/<|>|#/, '')
248
+ class_name.gsub!(/\/|:/, "_")
249
+
250
+ method_name = "_callback_#{kind}_#{class_name}"
251
+ method_name << "_#{next_id}" if append_next_id
252
+ method_name
253
+ end
254
+
237
255
  # Filters support:
238
256
  #
239
257
  # Arrays:: Used in conditions. This is used to specify
@@ -255,7 +273,8 @@ module ActiveSupport
255
273
  # a method is created that calls the before_foo method
256
274
  # on the object.
257
275
  def _compile_filter(filter)
258
- method_name = "_callback_#{@kind}_#{next_id}"
276
+ @_is_object_filter = false
277
+
259
278
  case filter
260
279
  when Array
261
280
  filter.map {|f| _compile_filter(f)}
@@ -264,11 +283,14 @@ module ActiveSupport
264
283
  when String
265
284
  "(#{filter})"
266
285
  when Proc
286
+ method_name = "_callback_#{@kind}_#{next_id}"
267
287
  @klass.send(:define_method, method_name, &filter)
268
288
  return method_name if filter.arity <= 0
269
289
 
270
290
  method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
271
291
  else
292
+ method_name = _method_name_for_object_filter(kind, filter)
293
+ @_is_object_filter = true
272
294
  @klass.send(:define_method, "#{method_name}_object") { filter }
273
295
 
274
296
  _normalize_legacy_filter(kind, filter)
@@ -314,14 +336,11 @@ module ActiveSupport
314
336
  @config = {
315
337
  :terminator => "false",
316
338
  :scope => [ :kind ]
317
- }.merge(config)
339
+ }.merge!(config)
318
340
  end
319
341
 
320
342
  def compile
321
- method = []
322
- method << "value = nil"
323
- method << "halted = false"
324
-
343
+ method = ["value = nil", "halted = false"]
325
344
  callbacks = "value = !halted && (!block_given? || yield)"
326
345
  reverse_each do |callback|
327
346
  callbacks = callback.apply(callbacks)
@@ -395,7 +414,7 @@ module ActiveSupport
395
414
  # This is used internally to append, prepend and skip callbacks to the
396
415
  # CallbackChain.
397
416
  def __update_callbacks(name, filters = [], block = nil) #:nodoc:
398
- type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
417
+ type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
399
418
  options = filters.last.is_a?(Hash) ? filters.pop : {}
400
419
  filters.unshift(block) if block
401
420
 
@@ -1,4 +1,4 @@
1
- Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path|
1
+ Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path|
2
2
  next if File.basename(path, '.rb') == 'logger'
3
- require "active_support/core_ext/#{File.basename(path, '.rb')}"
3
+ require path
4
4
  end
@@ -12,7 +12,7 @@ class Array
12
12
  # pass an option key that doesn't exist in the list below, it will raise an
13
13
  # <tt>ArgumentError</tt>.
14
14
  #
15
- # Options:
15
+ # ==== Options
16
16
  #
17
17
  # * <tt>:words_connector</tt> - The sign or word used to join the elements
18
18
  # in arrays with two or more elements (default: ", ").
@@ -24,6 +24,8 @@ class Array
24
24
  # the connector options defined on the 'support.array' namespace in the
25
25
  # corresponding dictionary file.
26
26
  #
27
+ # ==== Examples
28
+ #
27
29
  # [].to_sentence # => ""
28
30
  # ['one'].to_sentence # => "one"
29
31
  # ['one', 'two'].to_sentence # => "one and two"
@@ -38,10 +40,10 @@ class Array
38
40
  # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ')
39
41
  # # => "one or two or at least three"
40
42
  #
41
- # Examples using <tt>:locale</tt> option:
43
+ # Using <tt>:locale</tt> option:
42
44
  #
43
45
  # # Given this locale dictionary:
44
- # #
46
+ # #
45
47
  # # es:
46
48
  # # support:
47
49
  # # array:
@@ -1,5 +1,5 @@
1
1
  class Array
2
- # *DEPRECATED*: Use +Array#uniq+ instead.
2
+ # *DEPRECATED*: Use <tt>Array#uniq</tt> instead.
3
3
  #
4
4
  # Returns a unique array based on the criteria in the block.
5
5
  #
@@ -9,7 +9,7 @@ class Array
9
9
  uniq(&block)
10
10
  end
11
11
 
12
- # *DEPRECATED*: Use +Array#uniq!+ instead.
12
+ # *DEPRECATED*: Use <tt>Array#uniq!</tt> instead.
13
13
  #
14
14
  # Same as +uniq_by+, but modifies +self+.
15
15
  def uniq_by!(&block)
@@ -1,6 +1,13 @@
1
1
  require 'benchmark'
2
2
 
3
3
  class << Benchmark
4
+ # Benchmark realtime in milliseconds.
5
+ #
6
+ # Benchmark.realtime { User.all }
7
+ # # => 8.0e-05
8
+ #
9
+ # Benchmark.ms { User.all }
10
+ # # => 0.074
4
11
  def ms
5
12
  1000 * realtime { yield }
6
13
  end
@@ -1,4 +1,5 @@
1
1
  require 'bigdecimal'
2
+ require 'bigdecimal/util'
2
3
  require 'yaml'
3
4
 
4
5
  class BigDecimal
@@ -44,7 +44,8 @@ class Class
44
44
  # Base.setting # => []
45
45
  # Subclass.setting # => [:foo]
46
46
  #
47
- # For convenience, a query method is defined as well:
47
+ # For convenience, an instance predicate method is defined as well.
48
+ # To skip it, pass <tt>instance_predicate: false</tt>.
48
49
  #
49
50
  # Subclass.setting? # => false
50
51
  #
@@ -72,43 +73,46 @@ class Class
72
73
  # double assignment is used to avoid "assigned but unused variable" warning
73
74
  instance_reader = instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
74
75
  instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
76
+ instance_predicate = options.fetch(:instance_predicate, true)
75
77
 
76
- # We use class_eval here rather than define_method because class_attribute
77
- # may be used in a performance sensitive context therefore the overhead that
78
- # define_method introduces may become significant.
79
78
  attrs.each do |name|
80
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
81
- def self.#{name}() nil end
82
- def self.#{name}?() !!#{name} end
79
+ define_singleton_method(name) { nil }
80
+ define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
83
81
 
84
- def self.#{name}=(val)
85
- singleton_class.class_eval do
86
- remove_possible_method(:#{name})
87
- define_method(:#{name}) { val }
88
- end
82
+ ivar = "@#{name}"
83
+
84
+ define_singleton_method("#{name}=") do |val|
85
+ singleton_class.class_eval do
86
+ remove_possible_method(name)
87
+ define_method(name) { val }
88
+ end
89
89
 
90
- if singleton_class?
91
- class_eval do
92
- remove_possible_method(:#{name})
93
- def #{name}
94
- defined?(@#{name}) ? @#{name} : singleton_class.#{name}
90
+ if singleton_class?
91
+ class_eval do
92
+ remove_possible_method(name)
93
+ define_method(name) do
94
+ if instance_variable_defined? ivar
95
+ instance_variable_get ivar
96
+ else
97
+ singleton_class.send name
95
98
  end
96
99
  end
97
100
  end
98
- val
99
101
  end
102
+ val
103
+ end
100
104
 
101
- if instance_reader
102
- remove_possible_method :#{name}
103
- def #{name}
104
- defined?(@#{name}) ? @#{name} : self.class.#{name}
105
- end
106
-
107
- def #{name}?
108
- !!#{name}
105
+ if instance_reader
106
+ remove_possible_method name
107
+ define_method(name) do
108
+ if instance_variable_defined?(ivar)
109
+ instance_variable_get ivar
110
+ else
111
+ self.class.public_send name
109
112
  end
110
113
  end
111
- RUBY
114
+ define_method("#{name}?") { !!public_send(name) } if instance_predicate
115
+ end
112
116
 
113
117
  attr_writer name if instance_writer
114
118
  end
@@ -32,7 +32,7 @@ class Class
32
32
  def cattr_reader(*syms)
33
33
  options = syms.extract_options!
34
34
  syms.each do |sym|
35
- raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
35
+ raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
36
36
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
37
37
  unless defined? @@#{sym}
38
38
  @@#{sym} = nil
@@ -93,7 +93,7 @@ class Class
93
93
  def cattr_writer(*syms)
94
94
  options = syms.extract_options!
95
95
  syms.each do |sym|
96
- raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
96
+ raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
97
97
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
98
98
  unless defined? @@#{sym}
99
99
  @@#{sym} = nil
@@ -93,7 +93,7 @@ module DateAndTime
93
93
 
94
94
  # Returns a new date/time at the end of the quarter.
95
95
  # Example: 31st March, 30th June, 30th September.
96
- # DateTIme objects will have a time set to 23:59:59.
96
+ # DateTime objects will have a time set to 23:59:59.
97
97
  def end_of_quarter
98
98
  last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
99
99
  beginning_of_month.change(:month => last_quarter_month).end_of_month
@@ -109,11 +109,11 @@ module DateAndTime
109
109
  alias :at_beginning_of_year :beginning_of_year
110
110
 
111
111
  # Returns a new date/time representing the given day in the next week.
112
- # Week is assumed to start on +start_day+, default is
113
- # +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
114
- # DateTime objects have their time set to 0:00.
115
- def next_week(start_day = Date.beginning_of_week)
116
- first_hour{ weeks_since(1).beginning_of_week.days_since(days_span(start_day)) }
112
+ # The +given_day_in_next_week+ defaults to the beginning of the week
113
+ # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+
114
+ # when set. +DateTime+ objects have their time set to 0:00.
115
+ def next_week(given_day_in_next_week = Date.beginning_of_week)
116
+ first_hour{ weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)) }
117
117
  end
118
118
 
119
119
  # Short-hand for months_since(1).
@@ -1,8 +1,10 @@
1
1
  class Module
2
- # Provides a delegate class method to easily expose contained objects' public methods
3
- # as your own. Pass one or more methods (specified as symbols or strings)
4
- # and the name of the target object via the <tt>:to</tt> option (also a symbol
5
- # or string). At least one method and the <tt>:to</tt> option are required.
2
+ # Provides a +delegate+ class method to easily expose contained objects'
3
+ # public methods as your own.
4
+ #
5
+ # The macro receives one or more method names (specified as symbols or
6
+ # strings) and the name of the target object via the <tt>:to</tt> option
7
+ # (also a symbol or string).
6
8
  #
7
9
  # Delegation is particularly useful with Active Record associations:
8
10
  #
@@ -89,29 +91,44 @@ class Module
89
91
  # invoice.customer_name # => 'John Doe'
90
92
  # invoice.customer_address # => 'Vimmersvej 13'
91
93
  #
92
- # If the delegate object is +nil+ an exception is raised, and that happens
93
- # no matter whether +nil+ responds to the delegated method. You can get a
94
- # +nil+ instead with the +:allow_nil+ option.
94
+ # If the target is +nil+ and does not respond to the delegated method a
95
+ # +NoMethodError+ is raised, as with any other value. Sometimes, however, it
96
+ # makes sense to be robust to that situation and that is the purpose of the
97
+ # <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and
98
+ # responds to the method, everything works as usual. But if it is +nil+ and
99
+ # does not respond to the delegated method, +nil+ is returned.
95
100
  #
96
- # class Foo
97
- # attr_accessor :bar
98
- # def initialize(bar = nil)
99
- # @bar = bar
100
- # end
101
- # delegate :zoo, to: :bar
101
+ # class User < ActiveRecord::Base
102
+ # has_one :profile
103
+ # delegate :age, to: :profile
102
104
  # end
103
105
  #
104
- # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
106
+ # User.new.age # raises NoMethodError: undefined method `age'
107
+ #
108
+ # But if not having a profile yet is fine and should not be an error
109
+ # condition:
110
+ #
111
+ # class User < ActiveRecord::Base
112
+ # has_one :profile
113
+ # delegate :age, to: :profile, allow_nil: true
114
+ # end
115
+ #
116
+ # User.new.age # nil
117
+ #
118
+ # Note that if the target is not +nil+ then the call is attempted regardless of the
119
+ # <tt>:allow_nil</tt> option, and thus an exception is still raised if said object
120
+ # does not respond to the method:
105
121
  #
106
122
  # class Foo
107
- # attr_accessor :bar
108
- # def initialize(bar = nil)
123
+ # def initialize(bar)
109
124
  # @bar = bar
110
125
  # end
111
- # delegate :zoo, to: :bar, allow_nil: true
126
+ #
127
+ # delegate :name, to: :@bar, allow_nil: true
112
128
  # end
113
129
  #
114
- # Foo.new.zoo # returns nil
130
+ # Foo.new("Bar").name # raises NoMethodError: undefined method `name'
131
+ #
115
132
  def delegate(*methods)
116
133
  options = methods.pop
117
134
  unless options.is_a?(Hash) && to = options[:to]
@@ -142,22 +159,31 @@ class Module
142
159
  # methods still accept two arguments.
143
160
  definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
144
161
 
162
+ # The following generated methods call the target exactly once, storing
163
+ # the returned value in a dummy variable.
164
+ #
165
+ # Reason is twofold: On one hand doing less calls is in general better.
166
+ # On the other hand it could be that the target has side-effects,
167
+ # whereas conceptualy, from the user point of view, the delegator should
168
+ # be doing one call.
145
169
  if allow_nil
146
- module_eval(<<-EOS, file, line - 2)
170
+ module_eval(<<-EOS, file, line - 3)
147
171
  def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
148
- if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
149
- #{to}.#{method}(#{definition}) # client.name(*args, &block)
172
+ _ = #{to} # _ = client
173
+ if !_.nil? || nil.respond_to?(:#{method}) # if !_.nil? || nil.respond_to?(:name)
174
+ _.#{method}(#{definition}) # _.name(*args, &block)
150
175
  end # end
151
176
  end # end
152
177
  EOS
153
178
  else
154
179
  exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
155
180
 
156
- module_eval(<<-EOS, file, line - 1)
181
+ module_eval(<<-EOS, file, line - 2)
157
182
  def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
158
- #{to}.#{method}(#{definition}) # client.name(*args, &block)
183
+ _ = #{to} # _ = client
184
+ _.#{method}(#{definition}) # _.name(*args, &block)
159
185
  rescue NoMethodError # rescue NoMethodError
160
- if #{to}.nil? # if client.nil?
186
+ if _.nil? # if _.nil?
161
187
  #{exception} # # add helpful message to the exception
162
188
  else # else
163
189
  raise # raise
@@ -14,8 +14,8 @@ class Module
14
14
  # method where you can implement your custom warning behavior.
15
15
  #
16
16
  # class MyLib::Deprecator
17
- # def deprecation_warning(deprecated_method_name, message, caller_backtrace)
18
- # message = "#{method_name} is deprecated and will be removed from MyLibrary | #{message}"
17
+ # def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
18
+ # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
19
19
  # Kernel.warn message
20
20
  # end
21
21
  # end
@@ -4,7 +4,6 @@ require 'active_support/core_ext/string/multibyte'
4
4
  require 'active_support/core_ext/string/starts_ends_with'
5
5
  require 'active_support/core_ext/string/inflections'
6
6
  require 'active_support/core_ext/string/access'
7
- require 'active_support/core_ext/string/xchar'
8
7
  require 'active_support/core_ext/string/behavior'
9
8
  require 'active_support/core_ext/string/output_safety'
10
9
  require 'active_support/core_ext/string/exclude'
@@ -20,19 +20,17 @@ class String
20
20
  return if parts.empty?
21
21
 
22
22
  now = Time.now
23
- offset = parts[:offset]
24
- utc_offset = form == :utc ? 0 : now.utc_offset
25
- adjustment = offset ? offset - utc_offset : 0
26
-
27
- Time.send(
28
- form,
23
+ time = Time.new(
29
24
  parts.fetch(:year, now.year),
30
25
  parts.fetch(:mon, now.month),
31
26
  parts.fetch(:mday, now.day),
32
27
  parts.fetch(:hour, 0),
33
28
  parts.fetch(:min, 0),
34
- parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0)
35
- ) - adjustment
29
+ parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
30
+ parts.fetch(:offset, form == :utc ? 0 : nil)
31
+ )
32
+
33
+ form == :utc ? time.utc : time.getlocal
36
34
  end
37
35
 
38
36
  # Converts a string to a Date value.