activesupport 5.0.7.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 (236) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1013 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/lib/active_support.rb +99 -0
  6. data/lib/active_support/all.rb +3 -0
  7. data/lib/active_support/array_inquirer.rb +44 -0
  8. data/lib/active_support/backtrace_cleaner.rb +103 -0
  9. data/lib/active_support/benchmarkable.rb +49 -0
  10. data/lib/active_support/builder.rb +6 -0
  11. data/lib/active_support/cache.rb +701 -0
  12. data/lib/active_support/cache/file_store.rb +204 -0
  13. data/lib/active_support/cache/mem_cache_store.rb +207 -0
  14. data/lib/active_support/cache/memory_store.rb +167 -0
  15. data/lib/active_support/cache/null_store.rb +41 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +172 -0
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +44 -0
  18. data/lib/active_support/callbacks.rb +791 -0
  19. data/lib/active_support/concern.rb +142 -0
  20. data/lib/active_support/concurrency/latch.rb +26 -0
  21. data/lib/active_support/concurrency/share_lock.rb +226 -0
  22. data/lib/active_support/configurable.rb +148 -0
  23. data/lib/active_support/core_ext.rb +4 -0
  24. data/lib/active_support/core_ext/array.rb +7 -0
  25. data/lib/active_support/core_ext/array/access.rb +90 -0
  26. data/lib/active_support/core_ext/array/conversions.rb +211 -0
  27. data/lib/active_support/core_ext/array/extract_options.rb +29 -0
  28. data/lib/active_support/core_ext/array/grouping.rb +107 -0
  29. data/lib/active_support/core_ext/array/inquiry.rb +17 -0
  30. data/lib/active_support/core_ext/array/prepend_and_append.rb +7 -0
  31. data/lib/active_support/core_ext/array/wrap.rb +46 -0
  32. data/lib/active_support/core_ext/benchmark.rb +14 -0
  33. data/lib/active_support/core_ext/big_decimal.rb +1 -0
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  35. data/lib/active_support/core_ext/class.rb +2 -0
  36. data/lib/active_support/core_ext/class/attribute.rb +128 -0
  37. data/lib/active_support/core_ext/class/attribute_accessors.rb +4 -0
  38. data/lib/active_support/core_ext/class/subclasses.rb +41 -0
  39. data/lib/active_support/core_ext/date.rb +5 -0
  40. data/lib/active_support/core_ext/date/acts_like.rb +8 -0
  41. data/lib/active_support/core_ext/date/blank.rb +12 -0
  42. data/lib/active_support/core_ext/date/calculations.rb +143 -0
  43. data/lib/active_support/core_ext/date/conversions.rb +95 -0
  44. data/lib/active_support/core_ext/date/zones.rb +6 -0
  45. data/lib/active_support/core_ext/date_and_time/calculations.rb +335 -0
  46. data/lib/active_support/core_ext/date_and_time/compatibility.rb +14 -0
  47. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  48. data/lib/active_support/core_ext/date_time.rb +5 -0
  49. data/lib/active_support/core_ext/date_time/acts_like.rb +14 -0
  50. data/lib/active_support/core_ext/date_time/blank.rb +12 -0
  51. data/lib/active_support/core_ext/date_time/calculations.rb +199 -0
  52. data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
  53. data/lib/active_support/core_ext/date_time/conversions.rb +105 -0
  54. data/lib/active_support/core_ext/digest/uuid.rb +51 -0
  55. data/lib/active_support/core_ext/enumerable.rb +146 -0
  56. data/lib/active_support/core_ext/file.rb +1 -0
  57. data/lib/active_support/core_ext/file/atomic.rb +68 -0
  58. data/lib/active_support/core_ext/hash.rb +9 -0
  59. data/lib/active_support/core_ext/hash/compact.rb +24 -0
  60. data/lib/active_support/core_ext/hash/conversions.rb +262 -0
  61. data/lib/active_support/core_ext/hash/deep_merge.rb +38 -0
  62. data/lib/active_support/core_ext/hash/except.rb +22 -0
  63. data/lib/active_support/core_ext/hash/indifferent_access.rb +23 -0
  64. data/lib/active_support/core_ext/hash/keys.rb +170 -0
  65. data/lib/active_support/core_ext/hash/reverse_merge.rb +22 -0
  66. data/lib/active_support/core_ext/hash/slice.rb +48 -0
  67. data/lib/active_support/core_ext/hash/transform_values.rb +29 -0
  68. data/lib/active_support/core_ext/integer.rb +3 -0
  69. data/lib/active_support/core_ext/integer/inflections.rb +29 -0
  70. data/lib/active_support/core_ext/integer/multiple.rb +10 -0
  71. data/lib/active_support/core_ext/integer/time.rb +29 -0
  72. data/lib/active_support/core_ext/kernel.rb +4 -0
  73. data/lib/active_support/core_ext/kernel/agnostics.rb +11 -0
  74. data/lib/active_support/core_ext/kernel/concern.rb +12 -0
  75. data/lib/active_support/core_ext/kernel/debugger.rb +3 -0
  76. data/lib/active_support/core_ext/kernel/reporting.rb +43 -0
  77. data/lib/active_support/core_ext/kernel/singleton_class.rb +6 -0
  78. data/lib/active_support/core_ext/load_error.rb +31 -0
  79. data/lib/active_support/core_ext/marshal.rb +22 -0
  80. data/lib/active_support/core_ext/module.rb +12 -0
  81. data/lib/active_support/core_ext/module/aliasing.rb +74 -0
  82. data/lib/active_support/core_ext/module/anonymous.rb +28 -0
  83. data/lib/active_support/core_ext/module/attr_internal.rb +36 -0
  84. data/lib/active_support/core_ext/module/attribute_accessors.rb +212 -0
  85. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +141 -0
  86. data/lib/active_support/core_ext/module/concerning.rb +135 -0
  87. data/lib/active_support/core_ext/module/delegation.rb +216 -0
  88. data/lib/active_support/core_ext/module/deprecation.rb +23 -0
  89. data/lib/active_support/core_ext/module/introspection.rb +68 -0
  90. data/lib/active_support/core_ext/module/method_transplanting.rb +3 -0
  91. data/lib/active_support/core_ext/module/qualified_const.rb +70 -0
  92. data/lib/active_support/core_ext/module/reachable.rb +8 -0
  93. data/lib/active_support/core_ext/module/remove_method.rb +35 -0
  94. data/lib/active_support/core_ext/name_error.rb +31 -0
  95. data/lib/active_support/core_ext/numeric.rb +4 -0
  96. data/lib/active_support/core_ext/numeric/bytes.rb +64 -0
  97. data/lib/active_support/core_ext/numeric/conversions.rb +144 -0
  98. data/lib/active_support/core_ext/numeric/inquiry.rb +26 -0
  99. data/lib/active_support/core_ext/numeric/time.rb +74 -0
  100. data/lib/active_support/core_ext/object.rb +14 -0
  101. data/lib/active_support/core_ext/object/acts_like.rb +10 -0
  102. data/lib/active_support/core_ext/object/blank.rb +143 -0
  103. data/lib/active_support/core_ext/object/conversions.rb +4 -0
  104. data/lib/active_support/core_ext/object/deep_dup.rb +53 -0
  105. data/lib/active_support/core_ext/object/duplicable.rb +124 -0
  106. data/lib/active_support/core_ext/object/inclusion.rb +27 -0
  107. data/lib/active_support/core_ext/object/instance_variables.rb +28 -0
  108. data/lib/active_support/core_ext/object/json.rb +205 -0
  109. data/lib/active_support/core_ext/object/to_param.rb +1 -0
  110. data/lib/active_support/core_ext/object/to_query.rb +84 -0
  111. data/lib/active_support/core_ext/object/try.rb +146 -0
  112. data/lib/active_support/core_ext/object/with_options.rb +69 -0
  113. data/lib/active_support/core_ext/range.rb +4 -0
  114. data/lib/active_support/core_ext/range/conversions.rb +31 -0
  115. data/lib/active_support/core_ext/range/each.rb +21 -0
  116. data/lib/active_support/core_ext/range/include_range.rb +23 -0
  117. data/lib/active_support/core_ext/range/overlaps.rb +8 -0
  118. data/lib/active_support/core_ext/regexp.rb +5 -0
  119. data/lib/active_support/core_ext/securerandom.rb +23 -0
  120. data/lib/active_support/core_ext/string.rb +13 -0
  121. data/lib/active_support/core_ext/string/access.rb +104 -0
  122. data/lib/active_support/core_ext/string/behavior.rb +6 -0
  123. data/lib/active_support/core_ext/string/conversions.rb +57 -0
  124. data/lib/active_support/core_ext/string/exclude.rb +11 -0
  125. data/lib/active_support/core_ext/string/filters.rb +102 -0
  126. data/lib/active_support/core_ext/string/indent.rb +43 -0
  127. data/lib/active_support/core_ext/string/inflections.rb +244 -0
  128. data/lib/active_support/core_ext/string/inquiry.rb +13 -0
  129. data/lib/active_support/core_ext/string/multibyte.rb +53 -0
  130. data/lib/active_support/core_ext/string/output_safety.rb +260 -0
  131. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -0
  132. data/lib/active_support/core_ext/string/strip.rb +23 -0
  133. data/lib/active_support/core_ext/string/zones.rb +14 -0
  134. data/lib/active_support/core_ext/struct.rb +3 -0
  135. data/lib/active_support/core_ext/time.rb +5 -0
  136. data/lib/active_support/core_ext/time/acts_like.rb +8 -0
  137. data/lib/active_support/core_ext/time/calculations.rb +290 -0
  138. data/lib/active_support/core_ext/time/compatibility.rb +14 -0
  139. data/lib/active_support/core_ext/time/conversions.rb +67 -0
  140. data/lib/active_support/core_ext/time/marshal.rb +3 -0
  141. data/lib/active_support/core_ext/time/zones.rb +111 -0
  142. data/lib/active_support/core_ext/uri.rb +24 -0
  143. data/lib/active_support/dependencies.rb +755 -0
  144. data/lib/active_support/dependencies/autoload.rb +77 -0
  145. data/lib/active_support/dependencies/interlock.rb +55 -0
  146. data/lib/active_support/deprecation.rb +43 -0
  147. data/lib/active_support/deprecation/behaviors.rb +90 -0
  148. data/lib/active_support/deprecation/instance_delegator.rb +37 -0
  149. data/lib/active_support/deprecation/method_wrappers.rb +70 -0
  150. data/lib/active_support/deprecation/proxy_wrappers.rb +149 -0
  151. data/lib/active_support/deprecation/reporting.rb +112 -0
  152. data/lib/active_support/descendants_tracker.rb +60 -0
  153. data/lib/active_support/duration.rb +235 -0
  154. data/lib/active_support/duration/iso8601_parser.rb +122 -0
  155. data/lib/active_support/duration/iso8601_serializer.rb +51 -0
  156. data/lib/active_support/evented_file_update_checker.rb +199 -0
  157. data/lib/active_support/execution_wrapper.rb +126 -0
  158. data/lib/active_support/executor.rb +6 -0
  159. data/lib/active_support/file_update_checker.rb +157 -0
  160. data/lib/active_support/gem_version.rb +15 -0
  161. data/lib/active_support/gzip.rb +36 -0
  162. data/lib/active_support/hash_with_indifferent_access.rb +329 -0
  163. data/lib/active_support/i18n.rb +13 -0
  164. data/lib/active_support/i18n_railtie.rb +115 -0
  165. data/lib/active_support/inflections.rb +70 -0
  166. data/lib/active_support/inflector.rb +7 -0
  167. data/lib/active_support/inflector/inflections.rb +242 -0
  168. data/lib/active_support/inflector/methods.rb +390 -0
  169. data/lib/active_support/inflector/transliterate.rb +112 -0
  170. data/lib/active_support/json.rb +2 -0
  171. data/lib/active_support/json/decoding.rb +74 -0
  172. data/lib/active_support/json/encoding.rb +127 -0
  173. data/lib/active_support/key_generator.rb +71 -0
  174. data/lib/active_support/lazy_load_hooks.rb +76 -0
  175. data/lib/active_support/locale/en.yml +135 -0
  176. data/lib/active_support/log_subscriber.rb +109 -0
  177. data/lib/active_support/log_subscriber/test_helper.rb +104 -0
  178. data/lib/active_support/logger.rb +106 -0
  179. data/lib/active_support/logger_silence.rb +28 -0
  180. data/lib/active_support/logger_thread_safe_level.rb +31 -0
  181. data/lib/active_support/message_encryptor.rb +114 -0
  182. data/lib/active_support/message_verifier.rb +134 -0
  183. data/lib/active_support/multibyte.rb +21 -0
  184. data/lib/active_support/multibyte/chars.rb +231 -0
  185. data/lib/active_support/multibyte/unicode.rb +413 -0
  186. data/lib/active_support/notifications.rb +212 -0
  187. data/lib/active_support/notifications/fanout.rb +157 -0
  188. data/lib/active_support/notifications/instrumenter.rb +91 -0
  189. data/lib/active_support/number_helper.rb +368 -0
  190. data/lib/active_support/number_helper/number_converter.rb +182 -0
  191. data/lib/active_support/number_helper/number_to_currency_converter.rb +44 -0
  192. data/lib/active_support/number_helper/number_to_delimited_converter.rb +28 -0
  193. data/lib/active_support/number_helper/number_to_human_converter.rb +68 -0
  194. data/lib/active_support/number_helper/number_to_human_size_converter.rb +62 -0
  195. data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
  196. data/lib/active_support/number_helper/number_to_phone_converter.rb +58 -0
  197. data/lib/active_support/number_helper/number_to_rounded_converter.rb +92 -0
  198. data/lib/active_support/option_merger.rb +25 -0
  199. data/lib/active_support/ordered_hash.rb +48 -0
  200. data/lib/active_support/ordered_options.rb +81 -0
  201. data/lib/active_support/per_thread_registry.rb +58 -0
  202. data/lib/active_support/proxy_object.rb +13 -0
  203. data/lib/active_support/rails.rb +27 -0
  204. data/lib/active_support/railtie.rb +51 -0
  205. data/lib/active_support/reloader.rb +129 -0
  206. data/lib/active_support/rescuable.rb +173 -0
  207. data/lib/active_support/security_utils.rb +27 -0
  208. data/lib/active_support/string_inquirer.rb +26 -0
  209. data/lib/active_support/subscriber.rb +120 -0
  210. data/lib/active_support/tagged_logging.rb +77 -0
  211. data/lib/active_support/test_case.rb +88 -0
  212. data/lib/active_support/testing/assertions.rb +99 -0
  213. data/lib/active_support/testing/autorun.rb +5 -0
  214. data/lib/active_support/testing/constant_lookup.rb +50 -0
  215. data/lib/active_support/testing/declarative.rb +26 -0
  216. data/lib/active_support/testing/deprecation.rb +36 -0
  217. data/lib/active_support/testing/file_fixtures.rb +34 -0
  218. data/lib/active_support/testing/isolation.rb +115 -0
  219. data/lib/active_support/testing/method_call_assertions.rb +41 -0
  220. data/lib/active_support/testing/setup_and_teardown.rb +50 -0
  221. data/lib/active_support/testing/stream.rb +42 -0
  222. data/lib/active_support/testing/tagged_logging.rb +25 -0
  223. data/lib/active_support/testing/time_helpers.rb +136 -0
  224. data/lib/active_support/time.rb +18 -0
  225. data/lib/active_support/time_with_zone.rb +511 -0
  226. data/lib/active_support/values/time_zone.rb +484 -0
  227. data/lib/active_support/values/unicode_tables.dat +0 -0
  228. data/lib/active_support/version.rb +8 -0
  229. data/lib/active_support/xml_mini.rb +209 -0
  230. data/lib/active_support/xml_mini/jdom.rb +181 -0
  231. data/lib/active_support/xml_mini/libxml.rb +77 -0
  232. data/lib/active_support/xml_mini/libxmlsax.rb +82 -0
  233. data/lib/active_support/xml_mini/nokogiri.rb +81 -0
  234. data/lib/active_support/xml_mini/nokogirisax.rb +85 -0
  235. data/lib/active_support/xml_mini/rexml.rb +128 -0
  236. metadata +350 -0
@@ -0,0 +1,141 @@
1
+ require 'active_support/core_ext/array/extract_options'
2
+
3
+ # Extends the module object with class/module and instance accessors for
4
+ # class/module attributes, just like the native attr* accessors for instance
5
+ # attributes, but does so on a per-thread basis.
6
+ #
7
+ # So the values are scoped within the Thread.current space under the class name
8
+ # of the module.
9
+ class Module
10
+ # Defines a per-thread class attribute and creates class and instance reader methods.
11
+ # The underlying per-thread class variable is set to +nil+, if it is not previously defined.
12
+ #
13
+ # module Current
14
+ # thread_mattr_reader :user
15
+ # end
16
+ #
17
+ # Current.user # => nil
18
+ # Thread.current[:attr_Current_user] = "DHH"
19
+ # Current.user # => "DHH"
20
+ #
21
+ # The attribute name must be a valid method name in Ruby.
22
+ #
23
+ # module Foo
24
+ # thread_mattr_reader :"1_Badname"
25
+ # end
26
+ # # => NameError: invalid attribute name: 1_Badname
27
+ #
28
+ # If you want to opt out the creation on the instance reader method, pass
29
+ # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
30
+ #
31
+ # class Current
32
+ # thread_mattr_reader :user, instance_reader: false
33
+ # end
34
+ #
35
+ # Current.new.user # => NoMethodError
36
+ def thread_mattr_reader(*syms) # :nodoc:
37
+ options = syms.extract_options!
38
+
39
+ syms.each do |sym|
40
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
41
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
42
+ def self.#{sym}
43
+ Thread.current["attr_"+ name + "_#{sym}"]
44
+ end
45
+ EOS
46
+
47
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
48
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
49
+ def #{sym}
50
+ Thread.current["attr_"+ self.class.name + "_#{sym}"]
51
+ end
52
+ EOS
53
+ end
54
+ end
55
+ end
56
+ alias :thread_cattr_reader :thread_mattr_reader
57
+
58
+ # Defines a per-thread class attribute and creates a class and instance writer methods to
59
+ # allow assignment to the attribute.
60
+ #
61
+ # module Current
62
+ # thread_mattr_writer :user
63
+ # end
64
+ #
65
+ # Current.user = "DHH"
66
+ # Thread.current[:attr_Current_user] # => "DHH"
67
+ #
68
+ # If you want to opt out the instance writer method, pass
69
+ # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
70
+ #
71
+ # class Current
72
+ # thread_mattr_writer :user, instance_writer: false
73
+ # end
74
+ #
75
+ # Current.new.user = "DHH" # => NoMethodError
76
+ def thread_mattr_writer(*syms) # :nodoc:
77
+ options = syms.extract_options!
78
+ syms.each do |sym|
79
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
80
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
81
+ def self.#{sym}=(obj)
82
+ Thread.current["attr_"+ name + "_#{sym}"] = obj
83
+ end
84
+ EOS
85
+
86
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
87
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
88
+ def #{sym}=(obj)
89
+ Thread.current["attr_"+ self.class.name + "_#{sym}"] = obj
90
+ end
91
+ EOS
92
+ end
93
+ end
94
+ end
95
+ alias :thread_cattr_writer :thread_mattr_writer
96
+
97
+ # Defines both class and instance accessors for class attributes.
98
+ #
99
+ # class Account
100
+ # thread_mattr_accessor :user
101
+ # end
102
+ #
103
+ # Account.user = "DHH"
104
+ # Account.user # => "DHH"
105
+ # Account.new.user # => "DHH"
106
+ #
107
+ # If a subclass changes the value, the parent class' value is not changed.
108
+ # Similarly, if the parent class changes the value, the value of subclasses
109
+ # is not changed.
110
+ #
111
+ # class Customer < Account
112
+ # end
113
+ #
114
+ # Customer.user = "Rafael"
115
+ # Customer.user # => "Rafael"
116
+ # Account.user # => "DHH"
117
+ #
118
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
119
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
120
+ #
121
+ # class Current
122
+ # thread_mattr_accessor :user, instance_writer: false, instance_reader: false
123
+ # end
124
+ #
125
+ # Current.new.user = "DHH" # => NoMethodError
126
+ # Current.new.user # => NoMethodError
127
+ #
128
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
129
+ #
130
+ # class Current
131
+ # mattr_accessor :user, instance_accessor: false
132
+ # end
133
+ #
134
+ # Current.new.user = "DHH" # => NoMethodError
135
+ # Current.new.user # => NoMethodError
136
+ def thread_mattr_accessor(*syms, &blk)
137
+ thread_mattr_reader(*syms, &blk)
138
+ thread_mattr_writer(*syms, &blk)
139
+ end
140
+ alias :thread_cattr_accessor :thread_mattr_accessor
141
+ end
@@ -0,0 +1,135 @@
1
+ require 'active_support/concern'
2
+
3
+ class Module
4
+ # = Bite-sized separation of concerns
5
+ #
6
+ # We often find ourselves with a medium-sized chunk of behavior that we'd
7
+ # like to extract, but only mix in to a single class.
8
+ #
9
+ # Extracting a plain old Ruby object to encapsulate it and collaborate or
10
+ # delegate to the original object is often a good choice, but when there's
11
+ # no additional state to encapsulate or we're making DSL-style declarations
12
+ # about the parent class, introducing new collaborators can obfuscate rather
13
+ # than simplify.
14
+ #
15
+ # The typical route is to just dump everything in a monolithic class, perhaps
16
+ # with a comment, as a least-bad alternative. Using modules in separate files
17
+ # means tedious sifting to get a big-picture view.
18
+ #
19
+ # = Dissatisfying ways to separate small concerns
20
+ #
21
+ # == Using comments:
22
+ #
23
+ # class Todo
24
+ # # Other todo implementation
25
+ # # ...
26
+ #
27
+ # ## Event tracking
28
+ # has_many :events
29
+ #
30
+ # before_create :track_creation
31
+ # after_destroy :track_deletion
32
+ #
33
+ # private
34
+ # def track_creation
35
+ # # ...
36
+ # end
37
+ # end
38
+ #
39
+ # == With an inline module:
40
+ #
41
+ # Noisy syntax.
42
+ #
43
+ # class Todo
44
+ # # Other todo implementation
45
+ # # ...
46
+ #
47
+ # module EventTracking
48
+ # extend ActiveSupport::Concern
49
+ #
50
+ # included do
51
+ # has_many :events
52
+ # before_create :track_creation
53
+ # after_destroy :track_deletion
54
+ # end
55
+ #
56
+ # private
57
+ # def track_creation
58
+ # # ...
59
+ # end
60
+ # end
61
+ # include EventTracking
62
+ # end
63
+ #
64
+ # == Mix-in noise exiled to its own file:
65
+ #
66
+ # Once our chunk of behavior starts pushing the scroll-to-understand-it
67
+ # boundary, we give in and move it to a separate file. At this size, the
68
+ # increased overhead can be a reasonable tradeoff even if it reduces our
69
+ # at-a-glance perception of how things work.
70
+ #
71
+ # class Todo
72
+ # # Other todo implementation
73
+ # # ...
74
+ #
75
+ # include TodoEventTracking
76
+ # end
77
+ #
78
+ # = Introducing Module#concerning
79
+ #
80
+ # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
81
+ # separate bite-sized concerns.
82
+ #
83
+ # class Todo
84
+ # # Other todo implementation
85
+ # # ...
86
+ #
87
+ # concerning :EventTracking do
88
+ # included do
89
+ # has_many :events
90
+ # before_create :track_creation
91
+ # after_destroy :track_deletion
92
+ # end
93
+ #
94
+ # private
95
+ # def track_creation
96
+ # # ...
97
+ # end
98
+ # end
99
+ # end
100
+ #
101
+ # Todo.ancestors
102
+ # # => [Todo, Todo::EventTracking, Object]
103
+ #
104
+ # This small step has some wonderful ripple effects. We can
105
+ # * grok the behavior of our class in one glance,
106
+ # * clean up monolithic junk-drawer classes by separating their concerns, and
107
+ # * stop leaning on protected/private for crude "this is internal stuff" modularity.
108
+ module Concerning
109
+ # Define a new concern and mix it in.
110
+ def concerning(topic, &block)
111
+ include concern(topic, &block)
112
+ end
113
+
114
+ # A low-cruft shortcut to define a concern.
115
+ #
116
+ # concern :EventTracking do
117
+ # ...
118
+ # end
119
+ #
120
+ # is equivalent to
121
+ #
122
+ # module EventTracking
123
+ # extend ActiveSupport::Concern
124
+ #
125
+ # ...
126
+ # end
127
+ def concern(topic, &module_definition)
128
+ const_set topic, Module.new {
129
+ extend ::ActiveSupport::Concern
130
+ module_eval(&module_definition)
131
+ }
132
+ end
133
+ end
134
+ include Concerning
135
+ end
@@ -0,0 +1,216 @@
1
+ require 'set'
2
+
3
+ class Module
4
+ # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
5
+ # option is not used.
6
+ class DelegationError < NoMethodError; end
7
+
8
+ RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
9
+ else elsif END end ensure false for if in module next nil not or redo rescue retry
10
+ return self super then true undef unless until when while yield)
11
+ DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
12
+ DELEGATION_RESERVED_METHOD_NAMES = Set.new(
13
+ RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
14
+ ).freeze
15
+
16
+ # Provides a +delegate+ class method to easily expose contained objects'
17
+ # public methods as your own.
18
+ #
19
+ # ==== Options
20
+ # * <tt>:to</tt> - Specifies the target object
21
+ # * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
22
+ # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ from being raised
23
+ #
24
+ # The macro receives one or more method names (specified as symbols or
25
+ # strings) and the name of the target object via the <tt>:to</tt> option
26
+ # (also a symbol or string).
27
+ #
28
+ # Delegation is particularly useful with Active Record associations:
29
+ #
30
+ # class Greeter < ActiveRecord::Base
31
+ # def hello
32
+ # 'hello'
33
+ # end
34
+ #
35
+ # def goodbye
36
+ # 'goodbye'
37
+ # end
38
+ # end
39
+ #
40
+ # class Foo < ActiveRecord::Base
41
+ # belongs_to :greeter
42
+ # delegate :hello, to: :greeter
43
+ # end
44
+ #
45
+ # Foo.new.hello # => "hello"
46
+ # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
47
+ #
48
+ # Multiple delegates to the same target are allowed:
49
+ #
50
+ # class Foo < ActiveRecord::Base
51
+ # belongs_to :greeter
52
+ # delegate :hello, :goodbye, to: :greeter
53
+ # end
54
+ #
55
+ # Foo.new.goodbye # => "goodbye"
56
+ #
57
+ # Methods can be delegated to instance variables, class variables, or constants
58
+ # by providing them as a symbols:
59
+ #
60
+ # class Foo
61
+ # CONSTANT_ARRAY = [0,1,2,3]
62
+ # @@class_array = [4,5,6,7]
63
+ #
64
+ # def initialize
65
+ # @instance_array = [8,9,10,11]
66
+ # end
67
+ # delegate :sum, to: :CONSTANT_ARRAY
68
+ # delegate :min, to: :@@class_array
69
+ # delegate :max, to: :@instance_array
70
+ # end
71
+ #
72
+ # Foo.new.sum # => 6
73
+ # Foo.new.min # => 4
74
+ # Foo.new.max # => 11
75
+ #
76
+ # It's also possible to delegate a method to the class by using +:class+:
77
+ #
78
+ # class Foo
79
+ # def self.hello
80
+ # "world"
81
+ # end
82
+ #
83
+ # delegate :hello, to: :class
84
+ # end
85
+ #
86
+ # Foo.new.hello # => "world"
87
+ #
88
+ # Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
89
+ # is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
90
+ # delegated to.
91
+ #
92
+ # Person = Struct.new(:name, :address)
93
+ #
94
+ # class Invoice < Struct.new(:client)
95
+ # delegate :name, :address, to: :client, prefix: true
96
+ # end
97
+ #
98
+ # john_doe = Person.new('John Doe', 'Vimmersvej 13')
99
+ # invoice = Invoice.new(john_doe)
100
+ # invoice.client_name # => "John Doe"
101
+ # invoice.client_address # => "Vimmersvej 13"
102
+ #
103
+ # It is also possible to supply a custom prefix.
104
+ #
105
+ # class Invoice < Struct.new(:client)
106
+ # delegate :name, :address, to: :client, prefix: :customer
107
+ # end
108
+ #
109
+ # invoice = Invoice.new(john_doe)
110
+ # invoice.customer_name # => 'John Doe'
111
+ # invoice.customer_address # => 'Vimmersvej 13'
112
+ #
113
+ # If the target is +nil+ and does not respond to the delegated method a
114
+ # +NoMethodError+ is raised, as with any other value. Sometimes, however, it
115
+ # makes sense to be robust to that situation and that is the purpose of the
116
+ # <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and
117
+ # responds to the method, everything works as usual. But if it is +nil+ and
118
+ # does not respond to the delegated method, +nil+ is returned.
119
+ #
120
+ # class User < ActiveRecord::Base
121
+ # has_one :profile
122
+ # delegate :age, to: :profile
123
+ # end
124
+ #
125
+ # User.new.age # raises NoMethodError: undefined method `age'
126
+ #
127
+ # But if not having a profile yet is fine and should not be an error
128
+ # condition:
129
+ #
130
+ # class User < ActiveRecord::Base
131
+ # has_one :profile
132
+ # delegate :age, to: :profile, allow_nil: true
133
+ # end
134
+ #
135
+ # User.new.age # nil
136
+ #
137
+ # Note that if the target is not +nil+ then the call is attempted regardless of the
138
+ # <tt>:allow_nil</tt> option, and thus an exception is still raised if said object
139
+ # does not respond to the method:
140
+ #
141
+ # class Foo
142
+ # def initialize(bar)
143
+ # @bar = bar
144
+ # end
145
+ #
146
+ # delegate :name, to: :@bar, allow_nil: true
147
+ # end
148
+ #
149
+ # Foo.new("Bar").name # raises NoMethodError: undefined method `name'
150
+ #
151
+ # The target method must be public, otherwise it will raise +NoMethodError+.
152
+ #
153
+ def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
154
+ unless to
155
+ raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
156
+ end
157
+
158
+ if prefix == true && to =~ /^[^a-z_]/
159
+ raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
160
+ end
161
+
162
+ method_prefix = \
163
+ if prefix
164
+ "#{prefix == true ? to : prefix}_"
165
+ else
166
+ ''
167
+ end
168
+
169
+ location = caller_locations(1, 1).first
170
+ file, line = location.path, location.lineno
171
+
172
+ to = to.to_s
173
+ to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
174
+
175
+ methods.each do |method|
176
+ # Attribute writer methods only accept one argument. Makes sure []=
177
+ # methods still accept two arguments.
178
+ definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
179
+
180
+ # The following generated method calls the target exactly once, storing
181
+ # the returned value in a dummy variable.
182
+ #
183
+ # Reason is twofold: On one hand doing less calls is in general better.
184
+ # On the other hand it could be that the target has side-effects,
185
+ # whereas conceptually, from the user point of view, the delegator should
186
+ # be doing one call.
187
+ if allow_nil
188
+ method_def = [
189
+ "def #{method_prefix}#{method}(#{definition})",
190
+ "_ = #{to}",
191
+ "if !_.nil? || nil.respond_to?(:#{method})",
192
+ " _.#{method}(#{definition})",
193
+ "end",
194
+ "end"
195
+ ].join ';'
196
+ else
197
+ exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
198
+
199
+ method_def = [
200
+ "def #{method_prefix}#{method}(#{definition})",
201
+ " _ = #{to}",
202
+ " _.#{method}(#{definition})",
203
+ "rescue NoMethodError => e",
204
+ " if _.nil? && e.name == :#{method}",
205
+ " #{exception}",
206
+ " else",
207
+ " raise",
208
+ " end",
209
+ "end"
210
+ ].join ';'
211
+ end
212
+
213
+ module_eval(method_def, file, line)
214
+ end
215
+ end
216
+ end