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.
- checksums.yaml +7 -0
- data/README.md +3 -0
- data/lib/core_ext/array/access.rb +76 -0
- data/lib/core_ext/array/conversions.rb +211 -0
- data/lib/core_ext/array/extract_options.rb +29 -0
- data/lib/core_ext/array/grouping.rb +116 -0
- data/lib/core_ext/array/inquiry.rb +17 -0
- data/lib/core_ext/array/prepend_and_append.rb +7 -0
- data/lib/core_ext/array/wrap.rb +46 -0
- data/lib/core_ext/array.rb +7 -0
- data/lib/core_ext/array_inquirer.rb +44 -0
- data/lib/core_ext/benchmark.rb +14 -0
- data/lib/core_ext/benchmarkable.rb +49 -0
- data/lib/core_ext/big_decimal/conversions.rb +14 -0
- data/lib/core_ext/big_decimal.rb +1 -0
- data/lib/core_ext/builder.rb +6 -0
- data/lib/core_ext/callbacks.rb +770 -0
- data/lib/core_ext/class/attribute.rb +128 -0
- data/lib/core_ext/class/attribute_accessors.rb +4 -0
- data/lib/core_ext/class/subclasses.rb +42 -0
- data/lib/core_ext/class.rb +2 -0
- data/lib/core_ext/concern.rb +142 -0
- data/lib/core_ext/configurable.rb +148 -0
- data/lib/core_ext/date/acts_like.rb +8 -0
- data/lib/core_ext/date/blank.rb +12 -0
- data/lib/core_ext/date/calculations.rb +143 -0
- data/lib/core_ext/date/conversions.rb +93 -0
- data/lib/core_ext/date/zones.rb +6 -0
- data/lib/core_ext/date.rb +5 -0
- data/lib/core_ext/date_and_time/calculations.rb +328 -0
- data/lib/core_ext/date_and_time/zones.rb +40 -0
- data/lib/core_ext/date_time/acts_like.rb +14 -0
- data/lib/core_ext/date_time/blank.rb +12 -0
- data/lib/core_ext/date_time/calculations.rb +177 -0
- data/lib/core_ext/date_time/conversions.rb +104 -0
- data/lib/core_ext/date_time/zones.rb +6 -0
- data/lib/core_ext/date_time.rb +5 -0
- data/lib/core_ext/deprecation/behaviors.rb +86 -0
- data/lib/core_ext/deprecation/instance_delegator.rb +24 -0
- data/lib/core_ext/deprecation/method_wrappers.rb +70 -0
- data/lib/core_ext/deprecation/proxy_wrappers.rb +149 -0
- data/lib/core_ext/deprecation/reporting.rb +105 -0
- data/lib/core_ext/deprecation.rb +43 -0
- data/lib/core_ext/digest/uuid.rb +51 -0
- data/lib/core_ext/duration.rb +157 -0
- data/lib/core_ext/enumerable.rb +106 -0
- data/lib/core_ext/file/atomic.rb +68 -0
- data/lib/core_ext/file.rb +1 -0
- data/lib/core_ext/hash/compact.rb +20 -0
- data/lib/core_ext/hash/conversions.rb +261 -0
- data/lib/core_ext/hash/deep_merge.rb +38 -0
- data/lib/core_ext/hash/except.rb +22 -0
- data/lib/core_ext/hash/indifferent_access.rb +23 -0
- data/lib/core_ext/hash/keys.rb +170 -0
- data/lib/core_ext/hash/reverse_merge.rb +22 -0
- data/lib/core_ext/hash/slice.rb +48 -0
- data/lib/core_ext/hash/transform_values.rb +29 -0
- data/lib/core_ext/hash.rb +9 -0
- data/lib/core_ext/hash_with_indifferent_access.rb +298 -0
- data/lib/core_ext/inflections.rb +70 -0
- data/lib/core_ext/inflector/inflections.rb +244 -0
- data/lib/core_ext/inflector/methods.rb +381 -0
- data/lib/core_ext/inflector/transliterate.rb +112 -0
- data/lib/core_ext/inflector.rb +7 -0
- data/lib/core_ext/integer/inflections.rb +29 -0
- data/lib/core_ext/integer/multiple.rb +10 -0
- data/lib/core_ext/integer/time.rb +29 -0
- data/lib/core_ext/integer.rb +3 -0
- data/lib/core_ext/json/decoding.rb +67 -0
- data/lib/core_ext/json/encoding.rb +127 -0
- data/lib/core_ext/json.rb +2 -0
- data/lib/core_ext/kernel/agnostics.rb +11 -0
- data/lib/core_ext/kernel/concern.rb +10 -0
- data/lib/core_ext/kernel/reporting.rb +41 -0
- data/lib/core_ext/kernel/singleton_class.rb +6 -0
- data/lib/core_ext/kernel.rb +4 -0
- data/lib/core_ext/load_error.rb +30 -0
- data/lib/core_ext/logger.rb +57 -0
- data/lib/core_ext/logger_silence.rb +24 -0
- data/lib/core_ext/marshal.rb +19 -0
- data/lib/core_ext/module/aliasing.rb +74 -0
- data/lib/core_ext/module/anonymous.rb +28 -0
- data/lib/core_ext/module/attr_internal.rb +36 -0
- data/lib/core_ext/module/attribute_accessors.rb +212 -0
- data/lib/core_ext/module/concerning.rb +135 -0
- data/lib/core_ext/module/delegation.rb +218 -0
- data/lib/core_ext/module/deprecation.rb +23 -0
- data/lib/core_ext/module/introspection.rb +62 -0
- data/lib/core_ext/module/method_transplanting.rb +3 -0
- data/lib/core_ext/module/qualified_const.rb +52 -0
- data/lib/core_ext/module/reachable.rb +8 -0
- data/lib/core_ext/module/remove_method.rb +35 -0
- data/lib/core_ext/module.rb +11 -0
- data/lib/core_ext/multibyte/chars.rb +231 -0
- data/lib/core_ext/multibyte/unicode.rb +388 -0
- data/lib/core_ext/multibyte.rb +21 -0
- data/lib/core_ext/name_error.rb +31 -0
- data/lib/core_ext/numeric/bytes.rb +64 -0
- data/lib/core_ext/numeric/conversions.rb +132 -0
- data/lib/core_ext/numeric/inquiry.rb +26 -0
- data/lib/core_ext/numeric/time.rb +74 -0
- data/lib/core_ext/numeric.rb +4 -0
- data/lib/core_ext/object/acts_like.rb +10 -0
- data/lib/core_ext/object/blank.rb +140 -0
- data/lib/core_ext/object/conversions.rb +4 -0
- data/lib/core_ext/object/deep_dup.rb +53 -0
- data/lib/core_ext/object/duplicable.rb +98 -0
- data/lib/core_ext/object/inclusion.rb +27 -0
- data/lib/core_ext/object/instance_variables.rb +28 -0
- data/lib/core_ext/object/json.rb +199 -0
- data/lib/core_ext/object/to_param.rb +1 -0
- data/lib/core_ext/object/to_query.rb +84 -0
- data/lib/core_ext/object/try.rb +146 -0
- data/lib/core_ext/object/with_options.rb +69 -0
- data/lib/core_ext/object.rb +14 -0
- data/lib/core_ext/option_merger.rb +25 -0
- data/lib/core_ext/ordered_hash.rb +48 -0
- data/lib/core_ext/ordered_options.rb +81 -0
- data/lib/core_ext/range/conversions.rb +34 -0
- data/lib/core_ext/range/each.rb +21 -0
- data/lib/core_ext/range/include_range.rb +23 -0
- data/lib/core_ext/range/overlaps.rb +8 -0
- data/lib/core_ext/range.rb +4 -0
- data/lib/core_ext/regexp.rb +5 -0
- data/lib/core_ext/rescuable.rb +119 -0
- data/lib/core_ext/securerandom.rb +23 -0
- data/lib/core_ext/security_utils.rb +20 -0
- data/lib/core_ext/string/access.rb +104 -0
- data/lib/core_ext/string/behavior.rb +6 -0
- data/lib/core_ext/string/conversions.rb +56 -0
- data/lib/core_ext/string/exclude.rb +11 -0
- data/lib/core_ext/string/filters.rb +102 -0
- data/lib/core_ext/string/indent.rb +43 -0
- data/lib/core_ext/string/inflections.rb +235 -0
- data/lib/core_ext/string/inquiry.rb +13 -0
- data/lib/core_ext/string/multibyte.rb +53 -0
- data/lib/core_ext/string/output_safety.rb +261 -0
- data/lib/core_ext/string/starts_ends_with.rb +4 -0
- data/lib/core_ext/string/strip.rb +23 -0
- data/lib/core_ext/string/zones.rb +14 -0
- data/lib/core_ext/string.rb +13 -0
- data/lib/core_ext/string_inquirer.rb +26 -0
- data/lib/core_ext/tagged_logging.rb +78 -0
- data/lib/core_ext/test_case.rb +88 -0
- data/lib/core_ext/testing/assertions.rb +99 -0
- data/lib/core_ext/testing/autorun.rb +12 -0
- data/lib/core_ext/testing/composite_filter.rb +54 -0
- data/lib/core_ext/testing/constant_lookup.rb +50 -0
- data/lib/core_ext/testing/declarative.rb +26 -0
- data/lib/core_ext/testing/deprecation.rb +36 -0
- data/lib/core_ext/testing/file_fixtures.rb +34 -0
- data/lib/core_ext/testing/isolation.rb +115 -0
- data/lib/core_ext/testing/method_call_assertions.rb +41 -0
- data/lib/core_ext/testing/setup_and_teardown.rb +50 -0
- data/lib/core_ext/testing/stream.rb +42 -0
- data/lib/core_ext/testing/tagged_logging.rb +25 -0
- data/lib/core_ext/testing/time_helpers.rb +134 -0
- data/lib/core_ext/time/acts_like.rb +8 -0
- data/lib/core_ext/time/calculations.rb +284 -0
- data/lib/core_ext/time/conversions.rb +66 -0
- data/lib/core_ext/time/zones.rb +95 -0
- data/lib/core_ext/time.rb +20 -0
- data/lib/core_ext/time_with_zone.rb +503 -0
- data/lib/core_ext/time_zone.rb +464 -0
- data/lib/core_ext/uri.rb +25 -0
- data/lib/core_ext/version.rb +3 -0
- data/lib/core_ext/xml_mini/jdom.rb +181 -0
- data/lib/core_ext/xml_mini/libxml.rb +79 -0
- data/lib/core_ext/xml_mini/libxmlsax.rb +85 -0
- data/lib/core_ext/xml_mini/nokogiri.rb +83 -0
- data/lib/core_ext/xml_mini/nokogirisax.rb +87 -0
- data/lib/core_ext/xml_mini/rexml.rb +130 -0
- data/lib/core_ext/xml_mini.rb +194 -0
- data/lib/core_ext.rb +3 -0
- 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
|