core_ext 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3 -0
  3. data/lib/core_ext/array/access.rb +76 -0
  4. data/lib/core_ext/array/conversions.rb +211 -0
  5. data/lib/core_ext/array/extract_options.rb +29 -0
  6. data/lib/core_ext/array/grouping.rb +116 -0
  7. data/lib/core_ext/array/inquiry.rb +17 -0
  8. data/lib/core_ext/array/prepend_and_append.rb +7 -0
  9. data/lib/core_ext/array/wrap.rb +46 -0
  10. data/lib/core_ext/array.rb +7 -0
  11. data/lib/core_ext/array_inquirer.rb +44 -0
  12. data/lib/core_ext/benchmark.rb +14 -0
  13. data/lib/core_ext/benchmarkable.rb +49 -0
  14. data/lib/core_ext/big_decimal/conversions.rb +14 -0
  15. data/lib/core_ext/big_decimal.rb +1 -0
  16. data/lib/core_ext/builder.rb +6 -0
  17. data/lib/core_ext/callbacks.rb +770 -0
  18. data/lib/core_ext/class/attribute.rb +128 -0
  19. data/lib/core_ext/class/attribute_accessors.rb +4 -0
  20. data/lib/core_ext/class/subclasses.rb +42 -0
  21. data/lib/core_ext/class.rb +2 -0
  22. data/lib/core_ext/concern.rb +142 -0
  23. data/lib/core_ext/configurable.rb +148 -0
  24. data/lib/core_ext/date/acts_like.rb +8 -0
  25. data/lib/core_ext/date/blank.rb +12 -0
  26. data/lib/core_ext/date/calculations.rb +143 -0
  27. data/lib/core_ext/date/conversions.rb +93 -0
  28. data/lib/core_ext/date/zones.rb +6 -0
  29. data/lib/core_ext/date.rb +5 -0
  30. data/lib/core_ext/date_and_time/calculations.rb +328 -0
  31. data/lib/core_ext/date_and_time/zones.rb +40 -0
  32. data/lib/core_ext/date_time/acts_like.rb +14 -0
  33. data/lib/core_ext/date_time/blank.rb +12 -0
  34. data/lib/core_ext/date_time/calculations.rb +177 -0
  35. data/lib/core_ext/date_time/conversions.rb +104 -0
  36. data/lib/core_ext/date_time/zones.rb +6 -0
  37. data/lib/core_ext/date_time.rb +5 -0
  38. data/lib/core_ext/deprecation/behaviors.rb +86 -0
  39. data/lib/core_ext/deprecation/instance_delegator.rb +24 -0
  40. data/lib/core_ext/deprecation/method_wrappers.rb +70 -0
  41. data/lib/core_ext/deprecation/proxy_wrappers.rb +149 -0
  42. data/lib/core_ext/deprecation/reporting.rb +105 -0
  43. data/lib/core_ext/deprecation.rb +43 -0
  44. data/lib/core_ext/digest/uuid.rb +51 -0
  45. data/lib/core_ext/duration.rb +157 -0
  46. data/lib/core_ext/enumerable.rb +106 -0
  47. data/lib/core_ext/file/atomic.rb +68 -0
  48. data/lib/core_ext/file.rb +1 -0
  49. data/lib/core_ext/hash/compact.rb +20 -0
  50. data/lib/core_ext/hash/conversions.rb +261 -0
  51. data/lib/core_ext/hash/deep_merge.rb +38 -0
  52. data/lib/core_ext/hash/except.rb +22 -0
  53. data/lib/core_ext/hash/indifferent_access.rb +23 -0
  54. data/lib/core_ext/hash/keys.rb +170 -0
  55. data/lib/core_ext/hash/reverse_merge.rb +22 -0
  56. data/lib/core_ext/hash/slice.rb +48 -0
  57. data/lib/core_ext/hash/transform_values.rb +29 -0
  58. data/lib/core_ext/hash.rb +9 -0
  59. data/lib/core_ext/hash_with_indifferent_access.rb +298 -0
  60. data/lib/core_ext/inflections.rb +70 -0
  61. data/lib/core_ext/inflector/inflections.rb +244 -0
  62. data/lib/core_ext/inflector/methods.rb +381 -0
  63. data/lib/core_ext/inflector/transliterate.rb +112 -0
  64. data/lib/core_ext/inflector.rb +7 -0
  65. data/lib/core_ext/integer/inflections.rb +29 -0
  66. data/lib/core_ext/integer/multiple.rb +10 -0
  67. data/lib/core_ext/integer/time.rb +29 -0
  68. data/lib/core_ext/integer.rb +3 -0
  69. data/lib/core_ext/json/decoding.rb +67 -0
  70. data/lib/core_ext/json/encoding.rb +127 -0
  71. data/lib/core_ext/json.rb +2 -0
  72. data/lib/core_ext/kernel/agnostics.rb +11 -0
  73. data/lib/core_ext/kernel/concern.rb +10 -0
  74. data/lib/core_ext/kernel/reporting.rb +41 -0
  75. data/lib/core_ext/kernel/singleton_class.rb +6 -0
  76. data/lib/core_ext/kernel.rb +4 -0
  77. data/lib/core_ext/load_error.rb +30 -0
  78. data/lib/core_ext/logger.rb +57 -0
  79. data/lib/core_ext/logger_silence.rb +24 -0
  80. data/lib/core_ext/marshal.rb +19 -0
  81. data/lib/core_ext/module/aliasing.rb +74 -0
  82. data/lib/core_ext/module/anonymous.rb +28 -0
  83. data/lib/core_ext/module/attr_internal.rb +36 -0
  84. data/lib/core_ext/module/attribute_accessors.rb +212 -0
  85. data/lib/core_ext/module/concerning.rb +135 -0
  86. data/lib/core_ext/module/delegation.rb +218 -0
  87. data/lib/core_ext/module/deprecation.rb +23 -0
  88. data/lib/core_ext/module/introspection.rb +62 -0
  89. data/lib/core_ext/module/method_transplanting.rb +3 -0
  90. data/lib/core_ext/module/qualified_const.rb +52 -0
  91. data/lib/core_ext/module/reachable.rb +8 -0
  92. data/lib/core_ext/module/remove_method.rb +35 -0
  93. data/lib/core_ext/module.rb +11 -0
  94. data/lib/core_ext/multibyte/chars.rb +231 -0
  95. data/lib/core_ext/multibyte/unicode.rb +388 -0
  96. data/lib/core_ext/multibyte.rb +21 -0
  97. data/lib/core_ext/name_error.rb +31 -0
  98. data/lib/core_ext/numeric/bytes.rb +64 -0
  99. data/lib/core_ext/numeric/conversions.rb +132 -0
  100. data/lib/core_ext/numeric/inquiry.rb +26 -0
  101. data/lib/core_ext/numeric/time.rb +74 -0
  102. data/lib/core_ext/numeric.rb +4 -0
  103. data/lib/core_ext/object/acts_like.rb +10 -0
  104. data/lib/core_ext/object/blank.rb +140 -0
  105. data/lib/core_ext/object/conversions.rb +4 -0
  106. data/lib/core_ext/object/deep_dup.rb +53 -0
  107. data/lib/core_ext/object/duplicable.rb +98 -0
  108. data/lib/core_ext/object/inclusion.rb +27 -0
  109. data/lib/core_ext/object/instance_variables.rb +28 -0
  110. data/lib/core_ext/object/json.rb +199 -0
  111. data/lib/core_ext/object/to_param.rb +1 -0
  112. data/lib/core_ext/object/to_query.rb +84 -0
  113. data/lib/core_ext/object/try.rb +146 -0
  114. data/lib/core_ext/object/with_options.rb +69 -0
  115. data/lib/core_ext/object.rb +14 -0
  116. data/lib/core_ext/option_merger.rb +25 -0
  117. data/lib/core_ext/ordered_hash.rb +48 -0
  118. data/lib/core_ext/ordered_options.rb +81 -0
  119. data/lib/core_ext/range/conversions.rb +34 -0
  120. data/lib/core_ext/range/each.rb +21 -0
  121. data/lib/core_ext/range/include_range.rb +23 -0
  122. data/lib/core_ext/range/overlaps.rb +8 -0
  123. data/lib/core_ext/range.rb +4 -0
  124. data/lib/core_ext/regexp.rb +5 -0
  125. data/lib/core_ext/rescuable.rb +119 -0
  126. data/lib/core_ext/securerandom.rb +23 -0
  127. data/lib/core_ext/security_utils.rb +20 -0
  128. data/lib/core_ext/string/access.rb +104 -0
  129. data/lib/core_ext/string/behavior.rb +6 -0
  130. data/lib/core_ext/string/conversions.rb +56 -0
  131. data/lib/core_ext/string/exclude.rb +11 -0
  132. data/lib/core_ext/string/filters.rb +102 -0
  133. data/lib/core_ext/string/indent.rb +43 -0
  134. data/lib/core_ext/string/inflections.rb +235 -0
  135. data/lib/core_ext/string/inquiry.rb +13 -0
  136. data/lib/core_ext/string/multibyte.rb +53 -0
  137. data/lib/core_ext/string/output_safety.rb +261 -0
  138. data/lib/core_ext/string/starts_ends_with.rb +4 -0
  139. data/lib/core_ext/string/strip.rb +23 -0
  140. data/lib/core_ext/string/zones.rb +14 -0
  141. data/lib/core_ext/string.rb +13 -0
  142. data/lib/core_ext/string_inquirer.rb +26 -0
  143. data/lib/core_ext/tagged_logging.rb +78 -0
  144. data/lib/core_ext/test_case.rb +88 -0
  145. data/lib/core_ext/testing/assertions.rb +99 -0
  146. data/lib/core_ext/testing/autorun.rb +12 -0
  147. data/lib/core_ext/testing/composite_filter.rb +54 -0
  148. data/lib/core_ext/testing/constant_lookup.rb +50 -0
  149. data/lib/core_ext/testing/declarative.rb +26 -0
  150. data/lib/core_ext/testing/deprecation.rb +36 -0
  151. data/lib/core_ext/testing/file_fixtures.rb +34 -0
  152. data/lib/core_ext/testing/isolation.rb +115 -0
  153. data/lib/core_ext/testing/method_call_assertions.rb +41 -0
  154. data/lib/core_ext/testing/setup_and_teardown.rb +50 -0
  155. data/lib/core_ext/testing/stream.rb +42 -0
  156. data/lib/core_ext/testing/tagged_logging.rb +25 -0
  157. data/lib/core_ext/testing/time_helpers.rb +134 -0
  158. data/lib/core_ext/time/acts_like.rb +8 -0
  159. data/lib/core_ext/time/calculations.rb +284 -0
  160. data/lib/core_ext/time/conversions.rb +66 -0
  161. data/lib/core_ext/time/zones.rb +95 -0
  162. data/lib/core_ext/time.rb +20 -0
  163. data/lib/core_ext/time_with_zone.rb +503 -0
  164. data/lib/core_ext/time_zone.rb +464 -0
  165. data/lib/core_ext/uri.rb +25 -0
  166. data/lib/core_ext/version.rb +3 -0
  167. data/lib/core_ext/xml_mini/jdom.rb +181 -0
  168. data/lib/core_ext/xml_mini/libxml.rb +79 -0
  169. data/lib/core_ext/xml_mini/libxmlsax.rb +85 -0
  170. data/lib/core_ext/xml_mini/nokogiri.rb +83 -0
  171. data/lib/core_ext/xml_mini/nokogirisax.rb +87 -0
  172. data/lib/core_ext/xml_mini/rexml.rb +130 -0
  173. data/lib/core_ext/xml_mini.rb +194 -0
  174. data/lib/core_ext.rb +3 -0
  175. metadata +310 -0
@@ -0,0 +1,28 @@
1
+ class Module
2
+ # A module may or may not have a name.
3
+ #
4
+ # module M; end
5
+ # M.name # => "M"
6
+ #
7
+ # m = Module.new
8
+ # m.name # => nil
9
+ #
10
+ # +anonymous?+ method returns true if module does not have a name, false otherwise:
11
+ #
12
+ # Module.new.anonymous? # => true
13
+ #
14
+ # module M; end
15
+ # M.anonymous? # => false
16
+ #
17
+ # A module gets a name when it is first assigned to a constant. Either
18
+ # via the +module+ or +class+ keyword or by an explicit assignment:
19
+ #
20
+ # m = Module.new # creates an anonymous module
21
+ # m.anonymous? # => true
22
+ # M = m # m gets a name here as a side-effect
23
+ # m.name # => "M"
24
+ # m.anonymous? # => false
25
+ def anonymous?
26
+ name.nil?
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ class Module
2
+ # Declares an attribute reader backed by an internally-named instance variable.
3
+ def attr_internal_reader(*attrs)
4
+ attrs.each {|attr_name| attr_internal_define(attr_name, :reader)}
5
+ end
6
+
7
+ # Declares an attribute writer backed by an internally-named instance variable.
8
+ def attr_internal_writer(*attrs)
9
+ attrs.each {|attr_name| attr_internal_define(attr_name, :writer)}
10
+ end
11
+
12
+ # Declares an attribute reader and writer backed by an internally-named instance
13
+ # variable.
14
+ def attr_internal_accessor(*attrs)
15
+ attr_internal_reader(*attrs)
16
+ attr_internal_writer(*attrs)
17
+ end
18
+ alias_method :attr_internal, :attr_internal_accessor
19
+
20
+ class << self; attr_accessor :attr_internal_naming_format end
21
+ self.attr_internal_naming_format = '@_%s'
22
+
23
+ private
24
+ def attr_internal_ivar_name(attr)
25
+ Module.attr_internal_naming_format % attr
26
+ end
27
+
28
+ def attr_internal_define(attr_name, type)
29
+ internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '')
30
+ # use native attr_* methods as they are faster on some Ruby implementations
31
+ send("attr_#{type}", internal_name)
32
+ attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
33
+ alias_method attr_name, internal_name
34
+ remove_method internal_name
35
+ end
36
+ end
@@ -0,0 +1,212 @@
1
+ require '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.
6
+ class Module
7
+ # Defines a class attribute and creates a class and instance reader methods.
8
+ # The underlying class variable is set to +nil+, if it is not previously
9
+ # defined.
10
+ #
11
+ # module HairColors
12
+ # mattr_reader :hair_colors
13
+ # end
14
+ #
15
+ # HairColors.hair_colors # => nil
16
+ # HairColors.class_variable_set("@@hair_colors", [:brown, :black])
17
+ # HairColors.hair_colors # => [:brown, :black]
18
+ #
19
+ # The attribute name must be a valid method name in Ruby.
20
+ #
21
+ # module Foo
22
+ # mattr_reader :"1_Badname"
23
+ # end
24
+ # # => NameError: invalid attribute name: 1_Badname
25
+ #
26
+ # If you want to opt out the creation on the instance reader method, pass
27
+ # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
28
+ #
29
+ # module HairColors
30
+ # mattr_writer :hair_colors, instance_reader: false
31
+ # end
32
+ #
33
+ # class Person
34
+ # include HairColors
35
+ # end
36
+ #
37
+ # Person.new.hair_colors # => NoMethodError
38
+ #
39
+ #
40
+ # Also, you can pass a block to set up the attribute with a default value.
41
+ #
42
+ # module HairColors
43
+ # cattr_reader :hair_colors do
44
+ # [:brown, :black, :blonde, :red]
45
+ # end
46
+ # end
47
+ #
48
+ # class Person
49
+ # include HairColors
50
+ # end
51
+ #
52
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
53
+ def mattr_reader(*syms)
54
+ options = syms.extract_options!
55
+ syms.each do |sym|
56
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/
57
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
58
+ @@#{sym} = nil unless defined? @@#{sym}
59
+
60
+ def self.#{sym}
61
+ @@#{sym}
62
+ end
63
+ EOS
64
+
65
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
66
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
67
+ def #{sym}
68
+ @@#{sym}
69
+ end
70
+ EOS
71
+ end
72
+ class_variable_set("@@#{sym}", yield) if block_given?
73
+ end
74
+ end
75
+ alias :cattr_reader :mattr_reader
76
+
77
+ # Defines a class attribute and creates a class and instance writer methods to
78
+ # allow assignment to the attribute.
79
+ #
80
+ # module HairColors
81
+ # mattr_writer :hair_colors
82
+ # end
83
+ #
84
+ # class Person
85
+ # include HairColors
86
+ # end
87
+ #
88
+ # HairColors.hair_colors = [:brown, :black]
89
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black]
90
+ # Person.new.hair_colors = [:blonde, :red]
91
+ # HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red]
92
+ #
93
+ # If you want to opt out the instance writer method, pass
94
+ # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
95
+ #
96
+ # module HairColors
97
+ # mattr_writer :hair_colors, instance_writer: false
98
+ # end
99
+ #
100
+ # class Person
101
+ # include HairColors
102
+ # end
103
+ #
104
+ # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
105
+ #
106
+ # Also, you can pass a block to set up the attribute with a default value.
107
+ #
108
+ # class HairColors
109
+ # mattr_writer :hair_colors do
110
+ # [:brown, :black, :blonde, :red]
111
+ # end
112
+ # end
113
+ #
114
+ # class Person
115
+ # include HairColors
116
+ # end
117
+ #
118
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
119
+ def mattr_writer(*syms)
120
+ options = syms.extract_options!
121
+ syms.each do |sym|
122
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/
123
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
124
+ @@#{sym} = nil unless defined? @@#{sym}
125
+
126
+ def self.#{sym}=(obj)
127
+ @@#{sym} = obj
128
+ end
129
+ EOS
130
+
131
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
132
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
133
+ def #{sym}=(obj)
134
+ @@#{sym} = obj
135
+ end
136
+ EOS
137
+ end
138
+ send("#{sym}=", yield) if block_given?
139
+ end
140
+ end
141
+ alias :cattr_writer :mattr_writer
142
+
143
+ # Defines both class and instance accessors for class attributes.
144
+ #
145
+ # module HairColors
146
+ # mattr_accessor :hair_colors
147
+ # end
148
+ #
149
+ # class Person
150
+ # include HairColors
151
+ # end
152
+ #
153
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
154
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
155
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
156
+ #
157
+ # If a subclass changes the value then that would also change the value for
158
+ # parent class. Similarly if parent class changes the value then that would
159
+ # change the value of subclasses too.
160
+ #
161
+ # class Male < Person
162
+ # end
163
+ #
164
+ # Male.hair_colors << :blue
165
+ # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
166
+ #
167
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
168
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
169
+ #
170
+ # module HairColors
171
+ # mattr_accessor :hair_colors, instance_writer: false, instance_reader: false
172
+ # end
173
+ #
174
+ # class Person
175
+ # include HairColors
176
+ # end
177
+ #
178
+ # Person.new.hair_colors = [:brown] # => NoMethodError
179
+ # Person.new.hair_colors # => NoMethodError
180
+ #
181
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
182
+ #
183
+ # module HairColors
184
+ # mattr_accessor :hair_colors, instance_accessor: false
185
+ # end
186
+ #
187
+ # class Person
188
+ # include HairColors
189
+ # end
190
+ #
191
+ # Person.new.hair_colors = [:brown] # => NoMethodError
192
+ # Person.new.hair_colors # => NoMethodError
193
+ #
194
+ # Also you can pass a block to set up the attribute with a default value.
195
+ #
196
+ # module HairColors
197
+ # mattr_accessor :hair_colors do
198
+ # [:brown, :black, :blonde, :red]
199
+ # end
200
+ # end
201
+ #
202
+ # class Person
203
+ # include HairColors
204
+ # end
205
+ #
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)
210
+ end
211
+ alias :cattr_accessor :mattr_accessor
212
+ end
@@ -0,0 +1,135 @@
1
+ require 'core_ext/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 CoreExt::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 CoreExt::Concern
124
+ #
125
+ # ...
126
+ # end
127
+ def concern(topic, &module_definition)
128
+ const_set topic, Module.new {
129
+ extend ::CoreExt::Concern
130
+ module_eval(&module_definition)
131
+ }
132
+ end
133
+ end
134
+ include Concerning
135
+ end
@@ -0,0 +1,218 @@
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
+ DELEGATION_RESERVED_METHOD_NAMES = Set.new(
9
+ %w(_ arg args alias and BEGIN begin block break case class def defined? do
10
+ else elsif END end ensure false for if in module next nil not or redo
11
+ rescue retry return self super then true undef unless until when while
12
+ yield)
13
+ ).freeze
14
+
15
+ # Provides a +delegate+ class method to easily expose contained objects'
16
+ # public methods as your own.
17
+ #
18
+ # ==== Options
19
+ # * <tt>:to</tt> - Specifies the target object
20
+ # * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
21
+ # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ from being raised
22
+ #
23
+ # The macro receives one or more method names (specified as symbols or
24
+ # strings) and the name of the target object via the <tt>:to</tt> option
25
+ # (also a symbol or string).
26
+ #
27
+ # Delegation is particularly useful with Active Record associations:
28
+ #
29
+ # class Greeter < ActiveRecord::Base
30
+ # def hello
31
+ # 'hello'
32
+ # end
33
+ #
34
+ # def goodbye
35
+ # 'goodbye'
36
+ # end
37
+ # end
38
+ #
39
+ # class Foo < ActiveRecord::Base
40
+ # belongs_to :greeter
41
+ # delegate :hello, to: :greeter
42
+ # end
43
+ #
44
+ # Foo.new.hello # => "hello"
45
+ # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
46
+ #
47
+ # Multiple delegates to the same target are allowed:
48
+ #
49
+ # class Foo < ActiveRecord::Base
50
+ # belongs_to :greeter
51
+ # delegate :hello, :goodbye, to: :greeter
52
+ # end
53
+ #
54
+ # Foo.new.goodbye # => "goodbye"
55
+ #
56
+ # Methods can be delegated to instance variables, class variables, or constants
57
+ # by providing them as a symbols:
58
+ #
59
+ # class Foo
60
+ # CONSTANT_ARRAY = [0,1,2,3]
61
+ # @@class_array = [4,5,6,7]
62
+ #
63
+ # def initialize
64
+ # @instance_array = [8,9,10,11]
65
+ # end
66
+ # delegate :sum, to: :CONSTANT_ARRAY
67
+ # delegate :min, to: :@@class_array
68
+ # delegate :max, to: :@instance_array
69
+ # end
70
+ #
71
+ # Foo.new.sum # => 6
72
+ # Foo.new.min # => 4
73
+ # Foo.new.max # => 11
74
+ #
75
+ # It's also possible to delegate a method to the class by using +:class+:
76
+ #
77
+ # class Foo
78
+ # def self.hello
79
+ # "world"
80
+ # end
81
+ #
82
+ # delegate :hello, to: :class
83
+ # end
84
+ #
85
+ # Foo.new.hello # => "world"
86
+ #
87
+ # Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
88
+ # is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
89
+ # delegated to.
90
+ #
91
+ # Person = Struct.new(:name, :address)
92
+ #
93
+ # class Invoice < Struct.new(:client)
94
+ # delegate :name, :address, to: :client, prefix: true
95
+ # end
96
+ #
97
+ # john_doe = Person.new('John Doe', 'Vimmersvej 13')
98
+ # invoice = Invoice.new(john_doe)
99
+ # invoice.client_name # => "John Doe"
100
+ # invoice.client_address # => "Vimmersvej 13"
101
+ #
102
+ # It is also possible to supply a custom prefix.
103
+ #
104
+ # class Invoice < Struct.new(:client)
105
+ # delegate :name, :address, to: :client, prefix: :customer
106
+ # end
107
+ #
108
+ # invoice = Invoice.new(john_doe)
109
+ # invoice.customer_name # => 'John Doe'
110
+ # invoice.customer_address # => 'Vimmersvej 13'
111
+ #
112
+ # If the target is +nil+ and does not respond to the delegated method a
113
+ # +NoMethodError+ is raised, as with any other value. Sometimes, however, it
114
+ # makes sense to be robust to that situation and that is the purpose of the
115
+ # <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and
116
+ # responds to the method, everything works as usual. But if it is +nil+ and
117
+ # does not respond to the delegated method, +nil+ is returned.
118
+ #
119
+ # class User < ActiveRecord::Base
120
+ # has_one :profile
121
+ # delegate :age, to: :profile
122
+ # end
123
+ #
124
+ # User.new.age # raises NoMethodError: undefined method `age'
125
+ #
126
+ # But if not having a profile yet is fine and should not be an error
127
+ # condition:
128
+ #
129
+ # class User < ActiveRecord::Base
130
+ # has_one :profile
131
+ # delegate :age, to: :profile, allow_nil: true
132
+ # end
133
+ #
134
+ # User.new.age # nil
135
+ #
136
+ # Note that if the target is not +nil+ then the call is attempted regardless of the
137
+ # <tt>:allow_nil</tt> option, and thus an exception is still raised if said object
138
+ # does not respond to the method:
139
+ #
140
+ # class Foo
141
+ # def initialize(bar)
142
+ # @bar = bar
143
+ # end
144
+ #
145
+ # delegate :name, to: :@bar, allow_nil: true
146
+ # end
147
+ #
148
+ # Foo.new("Bar").name # raises NoMethodError: undefined method `name'
149
+ #
150
+ # The target method must be public, otherwise it will raise +NoMethodError+.
151
+ #
152
+ def delegate(*methods)
153
+ options = methods.pop
154
+ unless options.is_a?(Hash) && to = options[: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
+ prefix, allow_nil = options.values_at(:prefix, :allow_nil)
159
+
160
+ if prefix == true && to =~ /^[^a-z_]/
161
+ raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
162
+ end
163
+
164
+ method_prefix = \
165
+ if prefix
166
+ "#{prefix == true ? to : prefix}_"
167
+ else
168
+ ''
169
+ end
170
+
171
+ file, line = caller(1, 1).first.split(':'.freeze, 2)
172
+ line = line.to_i
173
+
174
+ to = to.to_s
175
+ to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
176
+
177
+ methods.each do |method|
178
+ # Attribute writer methods only accept one argument. Makes sure []=
179
+ # methods still accept two arguments.
180
+ definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
181
+
182
+ # The following generated method calls the target exactly once, storing
183
+ # the returned value in a dummy variable.
184
+ #
185
+ # Reason is twofold: On one hand doing less calls is in general better.
186
+ # On the other hand it could be that the target has side-effects,
187
+ # whereas conceptually, from the user point of view, the delegator should
188
+ # be doing one call.
189
+ if allow_nil
190
+ method_def = [
191
+ "def #{method_prefix}#{method}(#{definition})",
192
+ "_ = #{to}",
193
+ "if !_.nil? || nil.respond_to?(:#{method})",
194
+ " _.#{method}(#{definition})",
195
+ "end",
196
+ "end"
197
+ ].join ';'
198
+ else
199
+ exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
200
+
201
+ method_def = [
202
+ "def #{method_prefix}#{method}(#{definition})",
203
+ " _ = #{to}",
204
+ " _.#{method}(#{definition})",
205
+ "rescue NoMethodError => e",
206
+ " if _.nil? && e.name == :#{method}",
207
+ " #{exception}",
208
+ " else",
209
+ " raise",
210
+ " end",
211
+ "end"
212
+ ].join ';'
213
+ end
214
+
215
+ module_eval(method_def, file, line)
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,23 @@
1
+ class Module
2
+ # deprecate :foo
3
+ # deprecate bar: 'message'
4
+ # deprecate :foo, :bar, baz: 'warning!', qux: 'gone!'
5
+ #
6
+ # You can also use custom deprecator instance:
7
+ #
8
+ # deprecate :foo, deprecator: MyLib::Deprecator.new
9
+ # deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new
10
+ #
11
+ # \Custom deprecators must respond to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt>
12
+ # method where you can implement your custom warning behavior.
13
+ #
14
+ # class MyLib::Deprecator
15
+ # def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
16
+ # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
17
+ # Kernel.warn message
18
+ # end
19
+ # end
20
+ def deprecate(*method_names)
21
+ CoreExt::Deprecation.deprecate_methods(self, *method_names)
22
+ end
23
+ end