activesupport 4.0.13 → 4.1.0.beta1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +283 -508
- data/README.rdoc +1 -1
- data/lib/active_support.rb +7 -1
- data/lib/active_support/backtrace_cleaner.rb +5 -5
- data/lib/active_support/benchmarkable.rb +0 -10
- data/lib/active_support/cache.rb +62 -26
- data/lib/active_support/cache/file_store.rb +27 -22
- data/lib/active_support/cache/mem_cache_store.rb +2 -2
- data/lib/active_support/cache/memory_store.rb +1 -0
- data/lib/active_support/cache/strategy/local_cache.rb +3 -0
- data/lib/active_support/callbacks.rb +416 -245
- data/lib/active_support/concern.rb +13 -5
- data/lib/active_support/core_ext.rb +0 -1
- data/lib/active_support/core_ext/array.rb +0 -1
- data/lib/active_support/core_ext/array/access.rb +2 -0
- data/lib/active_support/core_ext/array/conversions.rb +2 -17
- data/lib/active_support/core_ext/array/grouping.rb +24 -12
- data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -2
- data/lib/active_support/core_ext/class.rb +0 -1
- data/lib/active_support/core_ext/class/attribute.rb +1 -2
- data/lib/active_support/core_ext/class/attribute_accessors.rb +5 -169
- data/lib/active_support/core_ext/date/calculations.rb +10 -0
- data/lib/active_support/core_ext/date/conversions.rb +5 -6
- data/lib/active_support/core_ext/date/zones.rb +2 -33
- data/lib/active_support/core_ext/date_and_time/calculations.rb +30 -11
- data/lib/active_support/core_ext/date_and_time/zones.rb +41 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +12 -25
- data/lib/active_support/core_ext/date_time/conversions.rb +2 -0
- data/lib/active_support/core_ext/date_time/zones.rb +3 -21
- data/lib/active_support/core_ext/hash.rb +0 -1
- data/lib/active_support/core_ext/hash/conversions.rb +6 -3
- data/lib/active_support/core_ext/hash/deep_merge.rb +11 -22
- data/lib/active_support/core_ext/hash/indifferent_access.rb +1 -0
- data/lib/active_support/core_ext/hash/keys.rb +27 -47
- data/lib/active_support/core_ext/kernel/reporting.rb +2 -6
- data/lib/active_support/core_ext/module.rb +1 -0
- data/lib/active_support/core_ext/module/attribute_accessors.rb +160 -14
- data/lib/active_support/core_ext/module/concerning.rb +135 -0
- data/lib/active_support/core_ext/module/delegation.rb +14 -4
- data/lib/active_support/core_ext/module/deprecation.rb +0 -2
- data/lib/active_support/core_ext/module/introspection.rb +0 -16
- data/lib/active_support/core_ext/module/method_transplanting.rb +11 -0
- data/lib/active_support/core_ext/numeric/time.rb +8 -0
- data/lib/active_support/core_ext/object.rb +1 -1
- data/lib/active_support/core_ext/object/blank.rb +1 -1
- data/lib/active_support/core_ext/object/deep_dup.rb +6 -6
- data/lib/active_support/core_ext/object/inclusion.rb +4 -15
- data/lib/active_support/core_ext/object/json.rb +197 -0
- data/lib/active_support/core_ext/object/to_json.rb +4 -26
- data/lib/active_support/core_ext/object/to_param.rb +58 -1
- data/lib/active_support/core_ext/object/to_query.rb +7 -56
- data/lib/active_support/core_ext/object/try.rb +1 -1
- data/lib/active_support/core_ext/range/each.rb +2 -1
- data/lib/active_support/core_ext/string/access.rb +31 -31
- data/lib/active_support/core_ext/string/conversions.rb +9 -8
- data/lib/active_support/core_ext/string/exclude.rb +3 -3
- data/lib/active_support/core_ext/string/filters.rb +14 -4
- data/lib/active_support/core_ext/string/inflections.rb +11 -9
- data/lib/active_support/core_ext/string/output_safety.rb +65 -24
- data/lib/active_support/core_ext/string/zones.rb +1 -0
- data/lib/active_support/core_ext/thread.rb +4 -4
- data/lib/active_support/core_ext/time/calculations.rb +10 -57
- data/lib/active_support/core_ext/time/conversions.rb +3 -1
- data/lib/active_support/core_ext/time/zones.rb +2 -21
- data/lib/active_support/dependencies.rb +29 -13
- data/lib/active_support/deprecation.rb +4 -4
- data/lib/active_support/deprecation/behaviors.rb +3 -3
- data/lib/active_support/duration.rb +5 -7
- data/lib/active_support/file_update_checker.rb +1 -1
- data/lib/active_support/hash_with_indifferent_access.rb +4 -9
- data/lib/active_support/i18n.rb +4 -4
- data/lib/active_support/i18n_railtie.rb +2 -6
- data/lib/active_support/inflections.rb +0 -1
- data/lib/active_support/inflector/inflections.rb +17 -17
- data/lib/active_support/inflector/methods.rb +34 -17
- data/lib/active_support/json/decoding.rb +14 -21
- data/lib/active_support/json/encoding.rb +113 -285
- data/lib/active_support/key_generator.rb +1 -1
- data/lib/active_support/lazy_load_hooks.rb +1 -1
- data/lib/active_support/log_subscriber/test_helper.rb +1 -1
- data/lib/active_support/logger.rb +1 -1
- data/lib/active_support/message_encryptor.rb +3 -3
- data/lib/active_support/message_verifier.rb +6 -1
- data/lib/active_support/multibyte/chars.rb +1 -2
- data/lib/active_support/multibyte/unicode.rb +27 -39
- data/lib/active_support/notifications.rb +3 -3
- data/lib/active_support/notifications/instrumenter.rb +2 -1
- data/lib/active_support/number_helper.rb +20 -311
- data/lib/active_support/number_helper/number_converter.rb +182 -0
- data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +21 -0
- data/lib/active_support/number_helper/number_to_human_converter.rb +66 -0
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +58 -0
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
- data/lib/active_support/number_helper/number_to_phone_converter.rb +49 -0
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +62 -0
- data/lib/active_support/option_merger.rb +1 -1
- data/lib/active_support/ordered_hash.rb +0 -8
- data/lib/active_support/ordered_options.rb +8 -0
- data/lib/active_support/per_thread_registry.rb +9 -8
- data/lib/active_support/subscriber.rb +26 -3
- data/lib/active_support/test_case.rb +9 -10
- data/lib/active_support/testing/assertions.rb +0 -30
- data/lib/active_support/testing/autorun.rb +2 -2
- data/lib/active_support/testing/declarative.rb +18 -8
- data/lib/active_support/testing/isolation.rb +13 -65
- data/lib/active_support/testing/setup_and_teardown.rb +17 -2
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +55 -0
- data/lib/active_support/time_with_zone.rb +4 -4
- data/lib/active_support/values/time_zone.rb +18 -15
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini.rb +2 -4
- metadata +71 -61
- data/lib/active_support/basic_object.rb +0 -11
- data/lib/active_support/buffered_logger.rb +0 -21
- data/lib/active_support/core_ext/array/uniq_by.rb +0 -19
- data/lib/active_support/core_ext/hash/diff.rb +0 -14
- data/lib/active_support/core_ext/logger.rb +0 -67
- data/lib/active_support/core_ext/proc.rb +0 -17
- data/lib/active_support/core_ext/string/encoding.rb +0 -8
- data/lib/active_support/json/variable.rb +0 -18
- data/lib/active_support/testing/pending.rb +0 -14
@@ -41,8 +41,6 @@ module Kernel
|
|
41
41
|
# end
|
42
42
|
#
|
43
43
|
# puts 'But this will'
|
44
|
-
#
|
45
|
-
# This method is not thread-safe.
|
46
44
|
def silence_stream(stream)
|
47
45
|
old_stream = stream.dup
|
48
46
|
stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
|
@@ -63,8 +61,7 @@ module Kernel
|
|
63
61
|
# puts 'This code gets executed and nothing related to ZeroDivisionError was seen'
|
64
62
|
def suppress(*exception_classes)
|
65
63
|
yield
|
66
|
-
rescue
|
67
|
-
raise unless exception_classes.any? { |cls| e.kind_of?(cls) }
|
64
|
+
rescue *exception_classes
|
68
65
|
end
|
69
66
|
|
70
67
|
# Captures the given stream and returns it:
|
@@ -94,6 +91,7 @@ module Kernel
|
|
94
91
|
stream_io.rewind
|
95
92
|
return captured_stream.read
|
96
93
|
ensure
|
94
|
+
captured_stream.close
|
97
95
|
captured_stream.unlink
|
98
96
|
stream_io.reopen(origin_stream)
|
99
97
|
end
|
@@ -102,8 +100,6 @@ module Kernel
|
|
102
100
|
# Silences both STDOUT and STDERR, even for subprocesses.
|
103
101
|
#
|
104
102
|
# quietly { system 'bundle install' }
|
105
|
-
#
|
106
|
-
# This method is not thread-safe.
|
107
103
|
def quietly
|
108
104
|
silence_stream(STDOUT) do
|
109
105
|
silence_stream(STDERR) do
|
@@ -4,6 +4,7 @@ require 'active_support/core_ext/module/anonymous'
|
|
4
4
|
require 'active_support/core_ext/module/reachable'
|
5
5
|
require 'active_support/core_ext/module/attribute_accessors'
|
6
6
|
require 'active_support/core_ext/module/attr_internal'
|
7
|
+
require 'active_support/core_ext/module/concerning'
|
7
8
|
require 'active_support/core_ext/module/delegation'
|
8
9
|
require 'active_support/core_ext/module/deprecation'
|
9
10
|
require 'active_support/core_ext/module/remove_method'
|
@@ -1,10 +1,59 @@
|
|
1
1
|
require 'active_support/core_ext/array/extract_options'
|
2
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.
|
3
6
|
class Module
|
7
|
+
# Defines a class attribute and creates a class and instance reader methods.
|
8
|
+
# The underlying the class variable is set to +nil+, if it is not previously
|
9
|
+
# defined.
|
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
|
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]
|
4
53
|
def mattr_reader(*syms)
|
5
54
|
options = syms.extract_options!
|
6
55
|
syms.each do |sym|
|
7
|
-
raise NameError.new(
|
56
|
+
raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
|
8
57
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
9
58
|
@@#{sym} = nil unless defined? @@#{sym}
|
10
59
|
|
@@ -20,14 +69,60 @@ class Module
|
|
20
69
|
end
|
21
70
|
EOS
|
22
71
|
end
|
72
|
+
class_variable_set("@@#{sym}", yield) if block_given?
|
23
73
|
end
|
24
74
|
end
|
75
|
+
alias :cattr_reader :mattr_reader
|
25
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]
|
26
119
|
def mattr_writer(*syms)
|
27
120
|
options = syms.extract_options!
|
28
121
|
syms.each do |sym|
|
29
|
-
raise NameError.new(
|
122
|
+
raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
|
30
123
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
124
|
+
@@#{sym} = nil unless defined? @@#{sym}
|
125
|
+
|
31
126
|
def self.#{sym}=(obj)
|
32
127
|
@@#{sym} = obj
|
33
128
|
end
|
@@ -40,27 +135,78 @@ class Module
|
|
40
135
|
end
|
41
136
|
EOS
|
42
137
|
end
|
138
|
+
send("#{sym}=", yield) if block_given?
|
43
139
|
end
|
44
140
|
end
|
141
|
+
alias :cattr_writer :mattr_writer
|
45
142
|
|
46
|
-
#
|
47
|
-
# just like the native attr* accessors for instance attributes.
|
143
|
+
# Defines both class and instance accessors for class attributes.
|
48
144
|
#
|
49
|
-
# module
|
50
|
-
# mattr_accessor :
|
145
|
+
# module HairColors
|
146
|
+
# mattr_accessor :hair_colors
|
147
|
+
# end
|
51
148
|
#
|
52
|
-
#
|
149
|
+
# class Person
|
150
|
+
# include HairColors
|
53
151
|
# end
|
54
152
|
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
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]
|
58
166
|
#
|
59
167
|
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
|
60
168
|
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
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, &blk)
|
65
210
|
end
|
211
|
+
alias :cattr_accessor :mattr_accessor
|
66
212
|
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
|
+
# overhead feels in good proportion to the size of our extraction, despite
|
69
|
+
# diluting our at-a-glance sense of how things really 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
|
@@ -1,7 +1,16 @@
|
|
1
1
|
class Module
|
2
|
+
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
|
3
|
+
# option is not used.
|
4
|
+
class DelegationError < NoMethodError; end
|
5
|
+
|
2
6
|
# Provides a +delegate+ class method to easily expose contained objects'
|
3
7
|
# public methods as your own.
|
4
8
|
#
|
9
|
+
# ==== Options
|
10
|
+
# * <tt>:to</tt> - Specifies the target object
|
11
|
+
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
|
12
|
+
# * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ to be raised
|
13
|
+
#
|
5
14
|
# The macro receives one or more method names (specified as symbols or
|
6
15
|
# strings) and the name of the target object via the <tt>:to</tt> option
|
7
16
|
# (also a symbol or string).
|
@@ -129,6 +138,8 @@ class Module
|
|
129
138
|
#
|
130
139
|
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
|
131
140
|
#
|
141
|
+
# The target method must be public, otherwise it will raise +NoMethodError+.
|
142
|
+
#
|
132
143
|
def delegate(*methods)
|
133
144
|
options = methods.pop
|
134
145
|
unless options.is_a?(Hash) && to = options[:to]
|
@@ -164,7 +175,7 @@ class Module
|
|
164
175
|
#
|
165
176
|
# Reason is twofold: On one hand doing less calls is in general better.
|
166
177
|
# On the other hand it could be that the target has side-effects,
|
167
|
-
# whereas
|
178
|
+
# whereas conceptually, from the user point of view, the delegator should
|
168
179
|
# be doing one call.
|
169
180
|
if allow_nil
|
170
181
|
module_eval(<<-EOS, file, line - 3)
|
@@ -176,15 +187,14 @@ class Module
|
|
176
187
|
end # end
|
177
188
|
EOS
|
178
189
|
else
|
179
|
-
exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
|
190
|
+
exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
|
180
191
|
|
181
192
|
module_eval(<<-EOS, file, line - 2)
|
182
193
|
def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
|
183
194
|
_ = #{to} # _ = client
|
184
195
|
_.#{method}(#{definition}) # _.name(*args, &block)
|
185
196
|
rescue NoMethodError => e # rescue NoMethodError => e
|
186
|
-
|
187
|
-
if _.nil? && e.backtrace.first == location # if _.nil? && e.backtrace.first == location
|
197
|
+
if _.nil? && e.name == :#{method} # if _.nil? && e.name == :name
|
188
198
|
#{exception} # # add helpful message to the exception
|
189
199
|
else # else
|
190
200
|
raise # raise
|