activesupport 4.2.11.3 → 5.2.8.1

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 (256) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +435 -403
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +4 -5
  5. data/lib/active_support/all.rb +5 -3
  6. data/lib/active_support/array_inquirer.rb +48 -0
  7. data/lib/active_support/backtrace_cleaner.rb +7 -5
  8. data/lib/active_support/benchmarkable.rb +6 -4
  9. data/lib/active_support/builder.rb +3 -1
  10. data/lib/active_support/cache/file_store.rb +41 -35
  11. data/lib/active_support/cache/mem_cache_store.rb +91 -91
  12. data/lib/active_support/cache/memory_store.rb +27 -30
  13. data/lib/active_support/cache/null_store.rb +7 -8
  14. data/lib/active_support/cache/redis_cache_store.rb +466 -0
  15. data/lib/active_support/cache/strategy/local_cache.rb +67 -34
  16. data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
  17. data/lib/active_support/cache.rb +287 -196
  18. data/lib/active_support/callbacks.rb +640 -590
  19. data/lib/active_support/concern.rb +11 -5
  20. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +35 -0
  21. data/lib/active_support/concurrency/share_lock.rb +227 -0
  22. data/lib/active_support/configurable.rb +8 -5
  23. data/lib/active_support/core_ext/array/access.rb +29 -1
  24. data/lib/active_support/core_ext/array/conversions.rb +22 -18
  25. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  26. data/lib/active_support/core_ext/array/grouping.rb +11 -18
  27. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  28. data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -3
  29. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  30. data/lib/active_support/core_ext/array.rb +9 -6
  31. data/lib/active_support/core_ext/benchmark.rb +3 -1
  32. data/lib/active_support/core_ext/big_decimal/conversions.rb +10 -12
  33. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  34. data/lib/active_support/core_ext/class/attribute.rb +41 -22
  35. data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
  36. data/lib/active_support/core_ext/class/subclasses.rb +20 -6
  37. data/lib/active_support/core_ext/class.rb +4 -3
  38. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  39. data/lib/active_support/core_ext/date/blank.rb +14 -0
  40. data/lib/active_support/core_ext/date/calculations.rb +11 -9
  41. data/lib/active_support/core_ext/date/conversions.rb +25 -23
  42. data/lib/active_support/core_ext/date/zones.rb +4 -2
  43. data/lib/active_support/core_ext/date.rb +6 -4
  44. data/lib/active_support/core_ext/date_and_time/calculations.rb +170 -58
  45. data/lib/active_support/core_ext/date_and_time/compatibility.rb +4 -3
  46. data/lib/active_support/core_ext/date_and_time/zones.rb +12 -12
  47. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  48. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  49. data/lib/active_support/core_ext/date_time/calculations.rb +36 -18
  50. data/lib/active_support/core_ext/date_time/compatibility.rb +8 -6
  51. data/lib/active_support/core_ext/date_time/conversions.rb +16 -12
  52. data/lib/active_support/core_ext/date_time.rb +7 -5
  53. data/lib/active_support/core_ext/digest/uuid.rb +7 -5
  54. data/lib/active_support/core_ext/digest.rb +3 -0
  55. data/lib/active_support/core_ext/enumerable.rb +101 -33
  56. data/lib/active_support/core_ext/file/atomic.rb +38 -31
  57. data/lib/active_support/core_ext/file.rb +3 -1
  58. data/lib/active_support/core_ext/hash/compact.rb +14 -9
  59. data/lib/active_support/core_ext/hash/conversions.rb +62 -41
  60. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  61. data/lib/active_support/core_ext/hash/except.rb +11 -8
  62. data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -3
  63. data/lib/active_support/core_ext/hash/keys.rb +33 -27
  64. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  65. data/lib/active_support/core_ext/hash/slice.rb +8 -8
  66. data/lib/active_support/core_ext/hash/transform_values.rb +14 -5
  67. data/lib/active_support/core_ext/hash.rb +11 -9
  68. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  69. data/lib/active_support/core_ext/integer/multiple.rb +2 -0
  70. data/lib/active_support/core_ext/integer/time.rb +11 -18
  71. data/lib/active_support/core_ext/integer.rb +5 -3
  72. data/lib/active_support/core_ext/kernel/agnostics.rb +2 -0
  73. data/lib/active_support/core_ext/kernel/concern.rb +5 -1
  74. data/lib/active_support/core_ext/kernel/reporting.rb +4 -84
  75. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  76. data/lib/active_support/core_ext/kernel.rb +6 -5
  77. data/lib/active_support/core_ext/load_error.rb +3 -22
  78. data/lib/active_support/core_ext/marshal.rb +8 -8
  79. data/lib/active_support/core_ext/module/aliasing.rb +6 -44
  80. data/lib/active_support/core_ext/module/anonymous.rb +12 -1
  81. data/lib/active_support/core_ext/module/attr_internal.rb +8 -9
  82. data/lib/active_support/core_ext/module/attribute_accessors.rb +43 -40
  83. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +150 -0
  84. data/lib/active_support/core_ext/module/concerning.rb +11 -12
  85. data/lib/active_support/core_ext/module/delegation.rb +98 -36
  86. data/lib/active_support/core_ext/module/deprecation.rb +4 -2
  87. data/lib/active_support/core_ext/module/introspection.rb +9 -9
  88. data/lib/active_support/core_ext/module/reachable.rb +5 -2
  89. data/lib/active_support/core_ext/module/redefine_method.rb +49 -0
  90. data/lib/active_support/core_ext/module/remove_method.rb +8 -3
  91. data/lib/active_support/core_ext/module.rb +14 -11
  92. data/lib/active_support/core_ext/name_error.rb +22 -2
  93. data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
  94. data/lib/active_support/core_ext/numeric/conversions.rb +78 -81
  95. data/lib/active_support/core_ext/numeric/inquiry.rb +28 -0
  96. data/lib/active_support/core_ext/numeric/time.rb +35 -23
  97. data/lib/active_support/core_ext/numeric.rb +6 -3
  98. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  99. data/lib/active_support/core_ext/object/blank.rb +27 -2
  100. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  101. data/lib/active_support/core_ext/object/deep_dup.rb +13 -4
  102. data/lib/active_support/core_ext/object/duplicable.rb +41 -14
  103. data/lib/active_support/core_ext/object/inclusion.rb +5 -3
  104. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  105. data/lib/active_support/core_ext/object/json.rb +49 -19
  106. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  107. data/lib/active_support/core_ext/object/to_query.rb +10 -5
  108. data/lib/active_support/core_ext/object/try.rb +69 -21
  109. data/lib/active_support/core_ext/object/with_options.rb +16 -3
  110. data/lib/active_support/core_ext/object.rb +14 -13
  111. data/lib/active_support/core_ext/range/compare_range.rb +61 -0
  112. data/lib/active_support/core_ext/range/conversions.rb +27 -7
  113. data/lib/active_support/core_ext/range/each.rb +19 -17
  114. data/lib/active_support/core_ext/range/include_range.rb +2 -22
  115. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  116. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  117. data/lib/active_support/core_ext/range.rb +7 -4
  118. data/lib/active_support/core_ext/regexp.rb +6 -0
  119. data/lib/active_support/core_ext/securerandom.rb +25 -0
  120. data/lib/active_support/core_ext/string/access.rb +8 -6
  121. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  122. data/lib/active_support/core_ext/string/conversions.rb +7 -4
  123. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  124. data/lib/active_support/core_ext/string/filters.rb +6 -5
  125. data/lib/active_support/core_ext/string/indent.rb +6 -4
  126. data/lib/active_support/core_ext/string/inflections.rb +61 -24
  127. data/lib/active_support/core_ext/string/inquiry.rb +3 -1
  128. data/lib/active_support/core_ext/string/multibyte.rb +15 -7
  129. data/lib/active_support/core_ext/string/output_safety.rb +62 -38
  130. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -0
  131. data/lib/active_support/core_ext/string/strip.rb +4 -5
  132. data/lib/active_support/core_ext/string/zones.rb +4 -2
  133. data/lib/active_support/core_ext/string.rb +15 -13
  134. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  135. data/lib/active_support/core_ext/time/calculations.rb +85 -51
  136. data/lib/active_support/core_ext/time/compatibility.rb +4 -2
  137. data/lib/active_support/core_ext/time/conversions.rb +20 -13
  138. data/lib/active_support/core_ext/time/zones.rb +41 -7
  139. data/lib/active_support/core_ext/time.rb +7 -6
  140. data/lib/active_support/core_ext/uri.rb +6 -8
  141. data/lib/active_support/core_ext.rb +3 -1
  142. data/lib/active_support/current_attributes.rb +195 -0
  143. data/lib/active_support/dependencies/autoload.rb +2 -0
  144. data/lib/active_support/dependencies/interlock.rb +57 -0
  145. data/lib/active_support/dependencies.rb +152 -161
  146. data/lib/active_support/deprecation/behaviors.rb +44 -11
  147. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  148. data/lib/active_support/deprecation/instance_delegator.rb +17 -2
  149. data/lib/active_support/deprecation/method_wrappers.rb +66 -20
  150. data/lib/active_support/deprecation/proxy_wrappers.rb +56 -28
  151. data/lib/active_support/deprecation/reporting.rb +32 -12
  152. data/lib/active_support/deprecation.rb +12 -9
  153. data/lib/active_support/descendants_tracker.rb +2 -0
  154. data/lib/active_support/digest.rb +20 -0
  155. data/lib/active_support/duration/iso8601_parser.rb +125 -0
  156. data/lib/active_support/duration/iso8601_serializer.rb +55 -0
  157. data/lib/active_support/duration.rb +314 -38
  158. data/lib/active_support/encrypted_configuration.rb +49 -0
  159. data/lib/active_support/encrypted_file.rb +99 -0
  160. data/lib/active_support/evented_file_update_checker.rb +205 -0
  161. data/lib/active_support/execution_wrapper.rb +131 -0
  162. data/lib/active_support/executor.rb +8 -0
  163. data/lib/active_support/file_update_checker.rb +63 -37
  164. data/lib/active_support/gem_version.rb +6 -4
  165. data/lib/active_support/gzip.rb +7 -5
  166. data/lib/active_support/hash_with_indifferent_access.rb +123 -28
  167. data/lib/active_support/i18n.rb +8 -6
  168. data/lib/active_support/i18n_railtie.rb +37 -13
  169. data/lib/active_support/inflections.rb +13 -11
  170. data/lib/active_support/inflector/inflections.rb +61 -12
  171. data/lib/active_support/inflector/methods.rb +163 -136
  172. data/lib/active_support/inflector/transliterate.rb +48 -27
  173. data/lib/active_support/inflector.rb +7 -5
  174. data/lib/active_support/json/decoding.rb +16 -13
  175. data/lib/active_support/json/encoding.rb +11 -58
  176. data/lib/active_support/json.rb +4 -2
  177. data/lib/active_support/key_generator.rb +25 -25
  178. data/lib/active_support/lazy_load_hooks.rb +50 -20
  179. data/lib/active_support/locale/en.yml +2 -0
  180. data/lib/active_support/log_subscriber/test_helper.rb +14 -12
  181. data/lib/active_support/log_subscriber.rb +13 -10
  182. data/lib/active_support/logger.rb +8 -7
  183. data/lib/active_support/logger_silence.rb +6 -4
  184. data/lib/active_support/logger_thread_safe_level.rb +7 -5
  185. data/lib/active_support/message_encryptor.rb +168 -53
  186. data/lib/active_support/message_verifier.rb +150 -17
  187. data/lib/active_support/messages/metadata.rb +71 -0
  188. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  189. data/lib/active_support/messages/rotator.rb +56 -0
  190. data/lib/active_support/multibyte/chars.rb +36 -23
  191. data/lib/active_support/multibyte/unicode.rb +100 -96
  192. data/lib/active_support/multibyte.rb +4 -2
  193. data/lib/active_support/notifications/fanout.rb +11 -9
  194. data/lib/active_support/notifications/instrumenter.rb +27 -7
  195. data/lib/active_support/notifications.rb +11 -7
  196. data/lib/active_support/number_helper/number_converter.rb +13 -11
  197. data/lib/active_support/number_helper/number_to_currency_converter.rb +9 -9
  198. data/lib/active_support/number_helper/number_to_delimited_converter.rb +9 -3
  199. data/lib/active_support/number_helper/number_to_human_converter.rb +11 -9
  200. data/lib/active_support/number_helper/number_to_human_size_converter.rb +9 -8
  201. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  202. data/lib/active_support/number_helper/number_to_phone_converter.rb +13 -4
  203. data/lib/active_support/number_helper/number_to_rounded_converter.rb +23 -56
  204. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  205. data/lib/active_support/number_helper.rb +94 -68
  206. data/lib/active_support/option_merger.rb +3 -1
  207. data/lib/active_support/ordered_hash.rb +6 -4
  208. data/lib/active_support/ordered_options.rb +23 -5
  209. data/lib/active_support/per_thread_registry.rb +9 -4
  210. data/lib/active_support/proxy_object.rb +2 -0
  211. data/lib/active_support/rails.rb +16 -8
  212. data/lib/active_support/railtie.rb +43 -9
  213. data/lib/active_support/reloader.rb +131 -0
  214. data/lib/active_support/rescuable.rb +108 -53
  215. data/lib/active_support/security_utils.rb +15 -11
  216. data/lib/active_support/string_inquirer.rb +11 -3
  217. data/lib/active_support/subscriber.rb +21 -16
  218. data/lib/active_support/tagged_logging.rb +14 -11
  219. data/lib/active_support/test_case.rb +19 -47
  220. data/lib/active_support/testing/assertions.rb +137 -20
  221. data/lib/active_support/testing/autorun.rb +4 -2
  222. data/lib/active_support/testing/constant_lookup.rb +2 -1
  223. data/lib/active_support/testing/declarative.rb +3 -1
  224. data/lib/active_support/testing/deprecation.rb +14 -10
  225. data/lib/active_support/testing/file_fixtures.rb +36 -0
  226. data/lib/active_support/testing/isolation.rb +34 -25
  227. data/lib/active_support/testing/method_call_assertions.rb +43 -0
  228. data/lib/active_support/testing/setup_and_teardown.rb +13 -8
  229. data/lib/active_support/testing/stream.rb +44 -0
  230. data/lib/active_support/testing/tagged_logging.rb +3 -1
  231. data/lib/active_support/testing/time_helpers.rb +81 -15
  232. data/lib/active_support/time.rb +14 -12
  233. data/lib/active_support/time_with_zone.rb +169 -39
  234. data/lib/active_support/values/time_zone.rb +196 -61
  235. data/lib/active_support/values/unicode_tables.dat +0 -0
  236. data/lib/active_support/version.rb +3 -1
  237. data/lib/active_support/xml_mini/jdom.rb +116 -114
  238. data/lib/active_support/xml_mini/libxml.rb +16 -13
  239. data/lib/active_support/xml_mini/libxmlsax.rb +15 -14
  240. data/lib/active_support/xml_mini/nokogiri.rb +14 -12
  241. data/lib/active_support/xml_mini/nokogirisax.rb +14 -13
  242. data/lib/active_support/xml_mini/rexml.rb +11 -9
  243. data/lib/active_support/xml_mini.rb +37 -37
  244. data/lib/active_support.rb +12 -11
  245. metadata +57 -27
  246. data/lib/active_support/concurrency/latch.rb +0 -27
  247. data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -16
  248. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
  249. data/lib/active_support/core_ext/date_time/zones.rb +0 -6
  250. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  251. data/lib/active_support/core_ext/module/method_transplanting.rb +0 -13
  252. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  253. data/lib/active_support/core_ext/object/itself.rb +0 -15
  254. data/lib/active_support/core_ext/struct.rb +0 -6
  255. data/lib/active_support/core_ext/thread.rb +0 -86
  256. data/lib/active_support/core_ext/time/marshal.rb +0 -30
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Module
2
4
  # Declares an attribute reader backed by an internally-named instance variable.
3
5
  def attr_internal_reader(*attrs)
4
- attrs.each {|attr_name| attr_internal_define(attr_name, :reader)}
6
+ attrs.each { |attr_name| attr_internal_define(attr_name, :reader) }
5
7
  end
6
8
 
7
9
  # Declares an attribute writer backed by an internally-named instance variable.
8
10
  def attr_internal_writer(*attrs)
9
- attrs.each {|attr_name| attr_internal_define(attr_name, :writer)}
11
+ attrs.each { |attr_name| attr_internal_define(attr_name, :writer) }
10
12
  end
11
13
 
12
14
  # Declares an attribute reader and writer backed by an internally-named instance
@@ -18,7 +20,7 @@ class Module
18
20
  alias_method :attr_internal, :attr_internal_accessor
19
21
 
20
22
  class << self; attr_accessor :attr_internal_naming_format end
21
- self.attr_internal_naming_format = '@_%s'
23
+ self.attr_internal_naming_format = "@_%s"
22
24
 
23
25
  private
24
26
  def attr_internal_ivar_name(attr)
@@ -26,12 +28,9 @@ class Module
26
28
  end
27
29
 
28
30
  def attr_internal_define(attr_name, type)
29
- internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '')
30
- # class_eval is necessary on 1.9 or else the methods are made private
31
- class_eval do
32
- # use native attr_* methods as they are faster on some Ruby implementations
33
- send("attr_#{type}", internal_name)
34
- end
31
+ internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, "")
32
+ # use native attr_* methods as they are faster on some Ruby implementations
33
+ send("attr_#{type}", internal_name)
35
34
  attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
36
35
  alias_method attr_name, internal_name
37
36
  remove_method internal_name
@@ -1,12 +1,16 @@
1
- require 'active_support/core_ext/array/extract_options'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/extract_options"
4
+ require "active_support/core_ext/regexp"
2
5
 
3
6
  # Extends the module object with class/module and instance accessors for
4
7
  # class/module attributes, just like the native attr* accessors for instance
5
8
  # attributes.
6
9
  class Module
7
10
  # Defines a class attribute and creates a class and instance reader methods.
8
- # The underlying the class variable is set to +nil+, if it is not previously
9
- # defined.
11
+ # The underlying class variable is set to +nil+, if it is not previously
12
+ # defined. All class and instance methods created will be public, even if
13
+ # this method is called with a private or protected access modifier.
10
14
  #
11
15
  # module HairColors
12
16
  # mattr_reader :hair_colors
@@ -19,15 +23,15 @@ class Module
19
23
  # The attribute name must be a valid method name in Ruby.
20
24
  #
21
25
  # module Foo
22
- # mattr_reader :"1_Badname "
26
+ # mattr_reader :"1_Badname"
23
27
  # end
24
- # # => NameError: invalid attribute name
28
+ # # => NameError: invalid attribute name: 1_Badname
25
29
  #
26
30
  # If you want to opt out the creation on the instance reader method, pass
27
31
  # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
28
32
  #
29
33
  # module HairColors
30
- # mattr_writer :hair_colors, instance_reader: false
34
+ # mattr_reader :hair_colors, instance_reader: false
31
35
  # end
32
36
  #
33
37
  # class Person
@@ -36,24 +40,20 @@ class Module
36
40
  #
37
41
  # Person.new.hair_colors # => NoMethodError
38
42
  #
39
- #
40
- # Also, you can pass a block to set up the attribute with a default value.
43
+ # You can set a default value for the attribute.
41
44
  #
42
45
  # module HairColors
43
- # cattr_reader :hair_colors do
44
- # [:brown, :black, :blonde, :red]
45
- # end
46
+ # mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
46
47
  # end
47
48
  #
48
49
  # class Person
49
50
  # include HairColors
50
51
  # end
51
52
  #
52
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
53
- def mattr_reader(*syms)
54
- options = syms.extract_options!
53
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
54
+ def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil)
55
55
  syms.each do |sym|
56
- raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
56
+ raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
57
57
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
58
58
  @@#{sym} = nil unless defined? @@#{sym}
59
59
 
@@ -62,20 +62,24 @@ class Module
62
62
  end
63
63
  EOS
64
64
 
65
- unless options[:instance_reader] == false || options[:instance_accessor] == false
65
+ if instance_reader && instance_accessor
66
66
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
67
67
  def #{sym}
68
68
  @@#{sym}
69
69
  end
70
70
  EOS
71
71
  end
72
- class_variable_set("@@#{sym}", yield) if block_given?
72
+
73
+ sym_default_value = (block_given? && default.nil?) ? yield : default
74
+ class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil?
73
75
  end
74
76
  end
75
77
  alias :cattr_reader :mattr_reader
76
78
 
77
79
  # Defines a class attribute and creates a class and instance writer methods to
78
- # allow assignment to the attribute.
80
+ # allow assignment to the attribute. All class and instance methods created
81
+ # will be public, even if this method is called with a private or protected
82
+ # access modifier.
79
83
  #
80
84
  # module HairColors
81
85
  # mattr_writer :hair_colors
@@ -103,12 +107,10 @@ class Module
103
107
  #
104
108
  # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
105
109
  #
106
- # Also, you can pass a block to set up the attribute with a default value.
110
+ # You can set a default value for the attribute.
107
111
  #
108
- # class HairColors
109
- # mattr_writer :hair_colors do
110
- # [:brown, :black, :blonde, :red]
111
- # end
112
+ # module HairColors
113
+ # mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red]
112
114
  # end
113
115
  #
114
116
  # class Person
@@ -116,10 +118,9 @@ class Module
116
118
  # end
117
119
  #
118
120
  # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
119
- def mattr_writer(*syms)
120
- options = syms.extract_options!
121
+ def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil)
121
122
  syms.each do |sym|
122
- raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
123
+ raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
123
124
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
124
125
  @@#{sym} = nil unless defined? @@#{sym}
125
126
 
@@ -128,19 +129,23 @@ class Module
128
129
  end
129
130
  EOS
130
131
 
131
- unless options[:instance_writer] == false || options[:instance_accessor] == false
132
+ if instance_writer && instance_accessor
132
133
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
133
134
  def #{sym}=(obj)
134
135
  @@#{sym} = obj
135
136
  end
136
137
  EOS
137
138
  end
138
- send("#{sym}=", yield) if block_given?
139
+
140
+ sym_default_value = (block_given? && default.nil?) ? yield : default
141
+ send("#{sym}=", sym_default_value) unless sym_default_value.nil?
139
142
  end
140
143
  end
141
144
  alias :cattr_writer :mattr_writer
142
145
 
143
146
  # Defines both class and instance accessors for class attributes.
147
+ # All class and instance methods created will be public, even if
148
+ # this method is called with a private or protected access modifier.
144
149
  #
145
150
  # module HairColors
146
151
  # mattr_accessor :hair_colors
@@ -150,8 +155,8 @@ class Module
150
155
  # include HairColors
151
156
  # end
152
157
  #
153
- # Person.hair_colors = [:brown, :black, :blonde, :red]
154
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
158
+ # HairColors.hair_colors = [:brown, :black, :blonde, :red]
159
+ # HairColors.hair_colors # => [:brown, :black, :blonde, :red]
155
160
  # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
156
161
  #
157
162
  # If a subclass changes the value then that would also change the value for
@@ -161,8 +166,8 @@ class Module
161
166
  # class Male < Person
162
167
  # end
163
168
  #
164
- # Male.hair_colors << :blue
165
- # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
169
+ # Male.new.hair_colors << :blue
170
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue]
166
171
  #
167
172
  # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
168
173
  # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
@@ -191,22 +196,20 @@ class Module
191
196
  # Person.new.hair_colors = [:brown] # => NoMethodError
192
197
  # Person.new.hair_colors # => NoMethodError
193
198
  #
194
- # Also you can pass a block to set up the attribute with a default value.
199
+ # You can set a default value for the attribute.
195
200
  #
196
201
  # module HairColors
197
- # mattr_accessor :hair_colors do
198
- # [:brown, :black, :blonde, :red]
199
- # end
202
+ # mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red]
200
203
  # end
201
204
  #
202
205
  # class Person
203
206
  # include HairColors
204
207
  # end
205
208
  #
206
- # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
207
- def mattr_accessor(*syms, &blk)
208
- mattr_reader(*syms, &blk)
209
- mattr_writer(*syms, &blk)
209
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
210
+ def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
211
+ mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk)
212
+ mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default)
210
213
  end
211
214
  alias :cattr_accessor :mattr_accessor
212
215
  end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/extract_options"
4
+ require "active_support/core_ext/regexp"
5
+
6
+ # Extends the module object with class/module and instance accessors for
7
+ # class/module attributes, just like the native attr* accessors for instance
8
+ # attributes, but does so on a per-thread basis.
9
+ #
10
+ # So the values are scoped within the Thread.current space under the class name
11
+ # of the module.
12
+ class Module
13
+ # Defines a per-thread class attribute and creates class and instance reader methods.
14
+ # The underlying per-thread class variable is set to +nil+, if it is not previously defined.
15
+ #
16
+ # module Current
17
+ # thread_mattr_reader :user
18
+ # end
19
+ #
20
+ # Current.user # => nil
21
+ # Thread.current[:attr_Current_user] = "DHH"
22
+ # Current.user # => "DHH"
23
+ #
24
+ # The attribute name must be a valid method name in Ruby.
25
+ #
26
+ # module Foo
27
+ # thread_mattr_reader :"1_Badname"
28
+ # end
29
+ # # => NameError: invalid attribute name: 1_Badname
30
+ #
31
+ # If you want to opt out of the creation of the instance reader method, pass
32
+ # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
33
+ #
34
+ # class Current
35
+ # thread_mattr_reader :user, instance_reader: false
36
+ # end
37
+ #
38
+ # Current.new.user # => NoMethodError
39
+ def thread_mattr_reader(*syms) # :nodoc:
40
+ options = syms.extract_options!
41
+
42
+ syms.each do |sym|
43
+ raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
44
+
45
+ # The following generated method concatenates `name` because we want it
46
+ # to work with inheritance via polymorphism.
47
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
48
+ def self.#{sym}
49
+ Thread.current["attr_" + name + "_#{sym}"]
50
+ end
51
+ EOS
52
+
53
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
54
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
55
+ def #{sym}
56
+ self.class.#{sym}
57
+ end
58
+ EOS
59
+ end
60
+ end
61
+ end
62
+ alias :thread_cattr_reader :thread_mattr_reader
63
+
64
+ # Defines a per-thread class attribute and creates a class and instance writer methods to
65
+ # allow assignment to the attribute.
66
+ #
67
+ # module Current
68
+ # thread_mattr_writer :user
69
+ # end
70
+ #
71
+ # Current.user = "DHH"
72
+ # Thread.current[:attr_Current_user] # => "DHH"
73
+ #
74
+ # If you want to opt out of the creation of the instance writer method, pass
75
+ # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
76
+ #
77
+ # class Current
78
+ # thread_mattr_writer :user, instance_writer: false
79
+ # end
80
+ #
81
+ # Current.new.user = "DHH" # => NoMethodError
82
+ def thread_mattr_writer(*syms) # :nodoc:
83
+ options = syms.extract_options!
84
+ syms.each do |sym|
85
+ raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
86
+
87
+ # The following generated method concatenates `name` because we want it
88
+ # to work with inheritance via polymorphism.
89
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
90
+ def self.#{sym}=(obj)
91
+ Thread.current["attr_" + name + "_#{sym}"] = obj
92
+ end
93
+ EOS
94
+
95
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
96
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
97
+ def #{sym}=(obj)
98
+ self.class.#{sym} = obj
99
+ end
100
+ EOS
101
+ end
102
+ end
103
+ end
104
+ alias :thread_cattr_writer :thread_mattr_writer
105
+
106
+ # Defines both class and instance accessors for class attributes.
107
+ #
108
+ # class Account
109
+ # thread_mattr_accessor :user
110
+ # end
111
+ #
112
+ # Account.user = "DHH"
113
+ # Account.user # => "DHH"
114
+ # Account.new.user # => "DHH"
115
+ #
116
+ # If a subclass changes the value, the parent class' value is not changed.
117
+ # Similarly, if the parent class changes the value, the value of subclasses
118
+ # is not changed.
119
+ #
120
+ # class Customer < Account
121
+ # end
122
+ #
123
+ # Customer.user = "Rafael"
124
+ # Customer.user # => "Rafael"
125
+ # Account.user # => "DHH"
126
+ #
127
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
128
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
129
+ #
130
+ # class Current
131
+ # thread_mattr_accessor :user, instance_writer: false, instance_reader: false
132
+ # end
133
+ #
134
+ # Current.new.user = "DHH" # => NoMethodError
135
+ # Current.new.user # => NoMethodError
136
+ #
137
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
138
+ #
139
+ # class Current
140
+ # mattr_accessor :user, instance_accessor: false
141
+ # end
142
+ #
143
+ # Current.new.user = "DHH" # => NoMethodError
144
+ # Current.new.user # => NoMethodError
145
+ def thread_mattr_accessor(*syms)
146
+ thread_mattr_reader(*syms)
147
+ thread_mattr_writer(*syms)
148
+ end
149
+ alias :thread_cattr_accessor :thread_mattr_accessor
150
+ end
@@ -1,4 +1,6 @@
1
- require 'active_support/concern'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
2
4
 
3
5
  class Module
4
6
  # = Bite-sized separation of concerns
@@ -20,7 +22,7 @@ class Module
20
22
  #
21
23
  # == Using comments:
22
24
  #
23
- # class Todo
25
+ # class Todo < ApplicationRecord
24
26
  # # Other todo implementation
25
27
  # # ...
26
28
  #
@@ -28,7 +30,6 @@ class Module
28
30
  # has_many :events
29
31
  #
30
32
  # before_create :track_creation
31
- # after_destroy :track_deletion
32
33
  #
33
34
  # private
34
35
  # def track_creation
@@ -40,7 +41,7 @@ class Module
40
41
  #
41
42
  # Noisy syntax.
42
43
  #
43
- # class Todo
44
+ # class Todo < ApplicationRecord
44
45
  # # Other todo implementation
45
46
  # # ...
46
47
  #
@@ -50,7 +51,6 @@ class Module
50
51
  # included do
51
52
  # has_many :events
52
53
  # before_create :track_creation
53
- # after_destroy :track_deletion
54
54
  # end
55
55
  #
56
56
  # private
@@ -63,12 +63,12 @@ class Module
63
63
  #
64
64
  # == Mix-in noise exiled to its own file:
65
65
  #
66
- # Once our chunk of behavior starts pushing the scroll-to-understand it's
66
+ # Once our chunk of behavior starts pushing the scroll-to-understand-it
67
67
  # boundary, we give in and move it to a separate file. At this size, the
68
- # overhead feels in good proportion to the size of our extraction, despite
69
- # diluting our at-a-glance sense of how things really work.
68
+ # increased overhead can be a reasonable tradeoff even if it reduces our
69
+ # at-a-glance perception of how things work.
70
70
  #
71
- # class Todo
71
+ # class Todo < ApplicationRecord
72
72
  # # Other todo implementation
73
73
  # # ...
74
74
  #
@@ -80,7 +80,7 @@ class Module
80
80
  # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
81
81
  # separate bite-sized concerns.
82
82
  #
83
- # class Todo
83
+ # class Todo < ApplicationRecord
84
84
  # # Other todo implementation
85
85
  # # ...
86
86
  #
@@ -88,7 +88,6 @@ class Module
88
88
  # included do
89
89
  # has_many :events
90
90
  # before_create :track_creation
91
- # after_destroy :track_deletion
92
91
  # end
93
92
  #
94
93
  # private
@@ -99,7 +98,7 @@ class Module
99
98
  # end
100
99
  #
101
100
  # Todo.ancestors
102
- # # => Todo, Todo::EventTracking, Object
101
+ # # => [Todo, Todo::EventTracking, ApplicationRecord, Object]
103
102
  #
104
103
  # This small step has some wonderful ripple effects. We can
105
104
  # * grok the behavior of our class in one glance,
@@ -1,22 +1,19 @@
1
- require 'set'
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "active_support/core_ext/regexp"
2
5
 
3
6
  class Module
4
7
  # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
5
8
  # option is not used.
6
9
  class DelegationError < NoMethodError; end
7
10
 
8
- RUBY_RESERVED_WORDS = Set.new(
9
- %w(alias and BEGIN begin break case class def defined? do else elsif END
10
- end ensure false for if in module next nil not or redo rescue retry
11
- return self super then true undef unless until when while yield)
12
- ).freeze
13
-
14
- DELEGATION_RESERVED_KEYWORDS = Set.new(
15
- %w(_ arg args block)
16
- )
17
-
11
+ RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
12
+ else elsif END end ensure false for if in module next nil not or redo rescue retry
13
+ return self super then true undef unless until when while yield)
14
+ DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
18
15
  DELEGATION_RESERVED_METHOD_NAMES = Set.new(
19
- RUBY_RESERVED_WORDS + DELEGATION_RESERVED_KEYWORDS
16
+ RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
20
17
  ).freeze
21
18
 
22
19
  # Provides a +delegate+ class method to easily expose contained objects'
@@ -25,7 +22,8 @@ class Module
25
22
  # ==== Options
26
23
  # * <tt>:to</tt> - Specifies the target object
27
24
  # * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
28
- # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ to be raised
25
+ # * <tt>:allow_nil</tt> - if set to true, prevents a +Module::DelegationError+
26
+ # from being raised
29
27
  #
30
28
  # The macro receives one or more method names (specified as symbols or
31
29
  # strings) and the name of the target object via the <tt>:to</tt> option
@@ -117,18 +115,16 @@ class Module
117
115
  # invoice.customer_address # => 'Vimmersvej 13'
118
116
  #
119
117
  # If the target is +nil+ and does not respond to the delegated method a
120
- # +NoMethodError+ is raised, as with any other value. Sometimes, however, it
121
- # makes sense to be robust to that situation and that is the purpose of the
122
- # <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and
123
- # responds to the method, everything works as usual. But if it is +nil+ and
124
- # does not respond to the delegated method, +nil+ is returned.
118
+ # +Module::DelegationError+ is raised. If you wish to instead return +nil+,
119
+ # use the <tt>:allow_nil</tt> option.
125
120
  #
126
121
  # class User < ActiveRecord::Base
127
122
  # has_one :profile
128
123
  # delegate :age, to: :profile
129
124
  # end
130
125
  #
131
- # User.new.age # raises NoMethodError: undefined method `age'
126
+ # User.new.age
127
+ # # => Module::DelegationError: User#age delegated to profile.age, but profile is nil
132
128
  #
133
129
  # But if not having a profile yet is fine and should not be an error
134
130
  # condition:
@@ -155,36 +151,32 @@ class Module
155
151
  # Foo.new("Bar").name # raises NoMethodError: undefined method `name'
156
152
  #
157
153
  # The target method must be public, otherwise it will raise +NoMethodError+.
158
- #
159
- def delegate(*methods)
160
- options = methods.pop
161
- unless options.is_a?(Hash) && to = options[:to]
162
- raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
154
+ def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
155
+ unless to
156
+ raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)."
163
157
  end
164
158
 
165
- prefix, allow_nil = options.values_at(:prefix, :allow_nil)
166
-
167
- if prefix == true && to =~ /^[^a-z_]/
168
- raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
159
+ if prefix == true && /^[^a-z_]/.match?(to)
160
+ raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
169
161
  end
170
162
 
171
163
  method_prefix = \
172
164
  if prefix
173
165
  "#{prefix == true ? to : prefix}_"
174
166
  else
175
- ''
167
+ ""
176
168
  end
177
169
 
178
- file, line = caller.first.split(':', 2)
179
- line = line.to_i
170
+ location = caller_locations(1, 1).first
171
+ file, line = location.path, location.lineno
180
172
 
181
173
  to = to.to_s
182
- to = "self.#{to}" if RUBY_RESERVED_WORDS.include?(to)
174
+ to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
183
175
 
184
- methods.each do |method|
176
+ methods.map do |method|
185
177
  # Attribute writer methods only accept one argument. Makes sure []=
186
178
  # methods still accept two arguments.
187
- definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
179
+ definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block"
188
180
 
189
181
  # The following generated method calls the target exactly once, storing
190
182
  # the returned value in a dummy variable.
@@ -201,7 +193,7 @@ class Module
201
193
  " _.#{method}(#{definition})",
202
194
  "end",
203
195
  "end"
204
- ].join ';'
196
+ ].join ";"
205
197
  else
206
198
  exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
207
199
 
@@ -216,10 +208,80 @@ class Module
216
208
  " raise",
217
209
  " end",
218
210
  "end"
219
- ].join ';'
211
+ ].join ";"
220
212
  end
221
213
 
222
214
  module_eval(method_def, file, line)
223
215
  end
224
216
  end
217
+
218
+ # When building decorators, a common pattern may emerge:
219
+ #
220
+ # class Partition
221
+ # def initialize(event)
222
+ # @event = event
223
+ # end
224
+ #
225
+ # def person
226
+ # @event.detail.person || @event.creator
227
+ # end
228
+ #
229
+ # private
230
+ # def respond_to_missing?(name, include_private = false)
231
+ # @event.respond_to?(name, include_private)
232
+ # end
233
+ #
234
+ # def method_missing(method, *args, &block)
235
+ # @event.send(method, *args, &block)
236
+ # end
237
+ # end
238
+ #
239
+ # With <tt>Module#delegate_missing_to</tt>, the above is condensed to:
240
+ #
241
+ # class Partition
242
+ # delegate_missing_to :@event
243
+ #
244
+ # def initialize(event)
245
+ # @event = event
246
+ # end
247
+ #
248
+ # def person
249
+ # @event.detail.person || @event.creator
250
+ # end
251
+ # end
252
+ #
253
+ # The target can be anything callable within the object, e.g. instance
254
+ # variables, methods, constants, etc.
255
+ #
256
+ # The delegated method must be public on the target, otherwise it will
257
+ # raise +NoMethodError+.
258
+ def delegate_missing_to(target)
259
+ target = target.to_s
260
+ target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
261
+
262
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
263
+ def respond_to_missing?(name, include_private = false)
264
+ # It may look like an oversight, but we deliberately do not pass
265
+ # +include_private+, because they do not get delegated.
266
+
267
+ #{target}.respond_to?(name) || super
268
+ end
269
+
270
+ def method_missing(method, *args, &block)
271
+ if #{target}.respond_to?(method)
272
+ #{target}.public_send(method, *args, &block)
273
+ else
274
+ begin
275
+ super
276
+ rescue NoMethodError
277
+ if #{target}.nil?
278
+ raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
279
+ else
280
+ raise
281
+ end
282
+ end
283
+ end
284
+ end
285
+ RUBY
286
+ end
225
287
  end