activesupport 1.3.1 → 1.4.0
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.
- data/CHANGELOG +232 -2
- data/README +43 -0
- data/lib/active_support.rb +4 -1
- data/lib/active_support/breakpoint.rb +5 -0
- data/lib/active_support/core_ext/array.rb +2 -16
- data/lib/active_support/core_ext/array/conversions.rb +30 -4
- data/lib/active_support/core_ext/array/grouping.rb +55 -0
- data/lib/active_support/core_ext/bigdecimal.rb +3 -0
- data/lib/active_support/core_ext/bigdecimal/formatting.rb +7 -0
- data/lib/active_support/core_ext/class/inheritable_attributes.rb +6 -1
- data/lib/active_support/core_ext/date/conversions.rb +13 -7
- data/lib/active_support/core_ext/enumerable.rb +41 -10
- data/lib/active_support/core_ext/exception.rb +2 -2
- data/lib/active_support/core_ext/hash/conversions.rb +123 -12
- data/lib/active_support/core_ext/hash/indifferent_access.rb +18 -9
- data/lib/active_support/core_ext/integer/inflections.rb +10 -4
- data/lib/active_support/core_ext/load_error.rb +3 -3
- data/lib/active_support/core_ext/module.rb +2 -0
- data/lib/active_support/core_ext/module/aliasing.rb +58 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +31 -0
- data/lib/active_support/core_ext/module/delegation.rb +27 -2
- data/lib/active_support/core_ext/name_error.rb +20 -0
- data/lib/active_support/core_ext/string.rb +2 -0
- data/lib/active_support/core_ext/string/access.rb +5 -5
- data/lib/active_support/core_ext/string/inflections.rb +93 -4
- data/lib/active_support/core_ext/string/unicode.rb +42 -0
- data/lib/active_support/core_ext/symbol.rb +1 -1
- data/lib/active_support/core_ext/time/calculations.rb +7 -5
- data/lib/active_support/core_ext/time/conversions.rb +1 -2
- data/lib/active_support/dependencies.rb +417 -50
- data/lib/active_support/deprecation.rb +201 -0
- data/lib/active_support/inflections.rb +1 -2
- data/lib/active_support/inflector.rb +117 -19
- data/lib/active_support/json.rb +14 -3
- data/lib/active_support/json/encoders/core.rb +21 -18
- data/lib/active_support/multibyte.rb +7 -0
- data/lib/active_support/multibyte/chars.rb +129 -0
- data/lib/active_support/multibyte/generators/generate_tables.rb +149 -0
- data/lib/active_support/multibyte/handlers/passthru_handler.rb +9 -0
- data/lib/active_support/multibyte/handlers/utf8_handler.rb +453 -0
- data/lib/active_support/multibyte/handlers/utf8_handler_proc.rb +44 -0
- data/lib/active_support/option_merger.rb +3 -3
- data/lib/active_support/ordered_options.rb +24 -23
- data/lib/active_support/reloadable.rb +39 -5
- data/lib/active_support/values/time_zone.rb +1 -1
- data/lib/active_support/values/unicode_tables.dat +0 -0
- data/lib/active_support/vendor/builder/blankslate.rb +16 -6
- data/lib/active_support/vendor/builder/xchar.rb +112 -0
- data/lib/active_support/vendor/builder/xmlbase.rb +12 -10
- data/lib/active_support/vendor/builder/xmlmarkup.rb +26 -7
- data/lib/active_support/vendor/xml_simple.rb +1021 -0
- data/lib/active_support/version.rb +2 -2
- data/lib/active_support/whiny_nil.rb +1 -1
- metadata +26 -4
- data/lib/active_support/core_ext/hash/conversions.rb.rej +0 -28
@@ -0,0 +1,31 @@
|
|
1
|
+
class Module
|
2
|
+
# Declare an attribute reader backed by an internally-named instance variable.
|
3
|
+
def attr_internal_reader(*attrs)
|
4
|
+
attrs.each do |attr|
|
5
|
+
module_eval "def #{attr}() #{attr_internal_ivar_name(attr)} end"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# Declare an attribute writer backed by an internally-named instance variable.
|
10
|
+
def attr_internal_writer(*attrs)
|
11
|
+
attrs.each do |attr|
|
12
|
+
module_eval "def #{attr}=(v) #{attr_internal_ivar_name(attr)} = v end"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Declare attributes backed by 'internal' instance variables names.
|
17
|
+
def attr_internal_accessor(*attrs)
|
18
|
+
attr_internal_reader(*attrs)
|
19
|
+
attr_internal_writer(*attrs)
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :attr_internal, :attr_internal_accessor
|
23
|
+
|
24
|
+
private
|
25
|
+
mattr_accessor :attr_internal_naming_format
|
26
|
+
self.attr_internal_naming_format = '@_%s'
|
27
|
+
|
28
|
+
def attr_internal_ivar_name(attr)
|
29
|
+
attr_internal_naming_format % attr
|
30
|
+
end
|
31
|
+
end
|
@@ -1,8 +1,33 @@
|
|
1
1
|
class Module
|
2
|
+
# Provides a delegate class method to easily expose contained objects' methods
|
3
|
+
# as your own. Pass one or more methods (specified as symbols or strings)
|
4
|
+
# and the name of the target object as the final :to option (also a symbol
|
5
|
+
# or string). At least one method and the :to option are required.
|
6
|
+
#
|
7
|
+
# Delegation is particularly useful with Active Record associations:
|
8
|
+
# class Greeter < ActiveRecord::Base
|
9
|
+
# def hello() "hello" end
|
10
|
+
# def goodbye() "goodbye" end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# class Foo < ActiveRecord::Base
|
14
|
+
# belongs_to :greeter
|
15
|
+
# delegate :hello, :to => :greeter
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Foo.new.hello # => "hello"
|
19
|
+
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
|
20
|
+
#
|
21
|
+
# Multiple delegates to the same target are allowed:
|
22
|
+
# class Foo < ActiveRecord::Base
|
23
|
+
# delegate :hello, :goodbye, :to => :greeter
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Foo.new.goodbye # => "goodbye"
|
2
27
|
def delegate(*methods)
|
3
28
|
options = methods.pop
|
4
29
|
unless options.is_a?(Hash) && to = options[:to]
|
5
|
-
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key"
|
30
|
+
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
|
6
31
|
end
|
7
32
|
|
8
33
|
methods.each do |method|
|
@@ -13,4 +38,4 @@ class Module
|
|
13
38
|
EOS
|
14
39
|
end
|
15
40
|
end
|
16
|
-
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
# Add a +missing_name+ method to NameError instances.
|
3
|
+
class NameError < StandardError
|
4
|
+
|
5
|
+
# Add a method to obtain the missing name from a NameError.
|
6
|
+
def missing_name
|
7
|
+
$1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message
|
8
|
+
end
|
9
|
+
|
10
|
+
# Was this exception raised because the given name was missing?
|
11
|
+
def missing_name?(name)
|
12
|
+
if name.is_a? Symbol
|
13
|
+
last_name = (missing_name || '').split('::').last
|
14
|
+
last_name == name.to_s
|
15
|
+
else
|
16
|
+
missing_name == name.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -3,6 +3,7 @@ require File.dirname(__FILE__) + '/string/conversions'
|
|
3
3
|
require File.dirname(__FILE__) + '/string/access'
|
4
4
|
require File.dirname(__FILE__) + '/string/starts_ends_with'
|
5
5
|
require File.dirname(__FILE__) + '/string/iterators'
|
6
|
+
require File.dirname(__FILE__) + '/string/unicode'
|
6
7
|
|
7
8
|
class String #:nodoc:
|
8
9
|
include ActiveSupport::CoreExtensions::String::Access
|
@@ -10,4 +11,5 @@ class String #:nodoc:
|
|
10
11
|
include ActiveSupport::CoreExtensions::String::Inflections
|
11
12
|
include ActiveSupport::CoreExtensions::String::StartsEndsWith
|
12
13
|
include ActiveSupport::CoreExtensions::String::Iterators
|
14
|
+
include ActiveSupport::CoreExtensions::String::Unicode
|
13
15
|
end
|
@@ -10,7 +10,7 @@ module ActiveSupport #:nodoc:
|
|
10
10
|
# "hello".at(4) # => "o"
|
11
11
|
# "hello".at(10) # => nil
|
12
12
|
def at(position)
|
13
|
-
|
13
|
+
chars[position, 1].to_s
|
14
14
|
end
|
15
15
|
|
16
16
|
# Returns the remaining of the string from the +position+ treating the string as an array (where 0 is the first character).
|
@@ -20,7 +20,7 @@ module ActiveSupport #:nodoc:
|
|
20
20
|
# "hello".from(2) # => "llo"
|
21
21
|
# "hello".from(10) # => nil
|
22
22
|
def from(position)
|
23
|
-
|
23
|
+
chars[position..-1].to_s
|
24
24
|
end
|
25
25
|
|
26
26
|
# Returns the beginning of the string up to the +position+ treating the string as an array (where 0 is the first character).
|
@@ -30,7 +30,7 @@ module ActiveSupport #:nodoc:
|
|
30
30
|
# "hello".to(2) # => "hel"
|
31
31
|
# "hello".to(10) # => "hello"
|
32
32
|
def to(position)
|
33
|
-
|
33
|
+
chars[0..position].to_s
|
34
34
|
end
|
35
35
|
|
36
36
|
# Returns the first character of the string or the first +limit+ characters.
|
@@ -40,7 +40,7 @@ module ActiveSupport #:nodoc:
|
|
40
40
|
# "hello".first(2) # => "he"
|
41
41
|
# "hello".first(10) # => "hello"
|
42
42
|
def first(limit = 1)
|
43
|
-
|
43
|
+
chars[0..(limit - 1)].to_s
|
44
44
|
end
|
45
45
|
|
46
46
|
# Returns the last character of the string or the last +limit+ characters.
|
@@ -50,7 +50,7 @@ module ActiveSupport #:nodoc:
|
|
50
50
|
# "hello".last(2) # => "lo"
|
51
51
|
# "hello".last(10) # => "hello"
|
52
52
|
def last(limit = 1)
|
53
|
-
|
53
|
+
(chars[(-limit)..-1] || self).to_s
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
@@ -1,17 +1,48 @@
|
|
1
|
-
require
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
2
3
|
module ActiveSupport #:nodoc:
|
3
4
|
module CoreExtensions #:nodoc:
|
4
5
|
module String #:nodoc:
|
5
|
-
#
|
6
|
+
# String inflections define new methods on the String class to transform names for different purposes.
|
7
|
+
# For instance, you can figure out the name of a database from the name of a class.
|
8
|
+
# "ScaleScore".tableize => "scale_scores"
|
6
9
|
module Inflections
|
10
|
+
# Returns the plural form of the word in the string.
|
11
|
+
#
|
12
|
+
# Examples
|
13
|
+
# "post".pluralize #=> "posts"
|
14
|
+
# "octopus".pluralize #=> "octopi"
|
15
|
+
# "sheep".pluralize #=> "sheep"
|
16
|
+
# "words".pluralize #=> "words"
|
17
|
+
# "the blue mailman".pluralize #=> "the blue mailmen"
|
18
|
+
# "CamelOctopus".pluralize #=> "CamelOctopi"
|
7
19
|
def pluralize
|
8
20
|
Inflector.pluralize(self)
|
9
21
|
end
|
10
22
|
|
23
|
+
# The reverse of pluralize, returns the singular form of a word in a string.
|
24
|
+
#
|
25
|
+
# Examples
|
26
|
+
# "posts".singularize #=> "post"
|
27
|
+
# "octopi".singularize #=> "octopus"
|
28
|
+
# "sheep".singluarize #=> "sheep"
|
29
|
+
# "word".singluarize #=> "word"
|
30
|
+
# "the blue mailmen".singularize #=> "the blue mailman"
|
31
|
+
# "CamelOctopi".singularize #=> "CamelOctopus"
|
11
32
|
def singularize
|
12
33
|
Inflector.singularize(self)
|
13
34
|
end
|
14
35
|
|
36
|
+
# By default, camelize converts strings to UpperCamelCase. If the argument to camelize
|
37
|
+
# is set to ":lower" then camelize produces lowerCamelCase.
|
38
|
+
#
|
39
|
+
# camelize will also convert '/' to '::' which is useful for converting paths to namespaces
|
40
|
+
#
|
41
|
+
# Examples
|
42
|
+
# "active_record".camelize #=> "ActiveRecord"
|
43
|
+
# "active_record".camelize(:lower) #=> "activeRecord"
|
44
|
+
# "active_record/errors".camelize #=> "ActiveRecord::Errors"
|
45
|
+
# "active_record/errors".camelize(:lower) #=> "activeRecord::Errors"
|
15
46
|
def camelize(first_letter = :upper)
|
16
47
|
case first_letter
|
17
48
|
when :upper then Inflector.camelize(self, true)
|
@@ -20,41 +51,99 @@ module ActiveSupport #:nodoc:
|
|
20
51
|
end
|
21
52
|
alias_method :camelcase, :camelize
|
22
53
|
|
54
|
+
# Capitalizes all the words and replaces some characters in the string to create
|
55
|
+
# a nicer looking title. Titleize is meant for creating pretty output. It is not
|
56
|
+
# used in the Rails internals.
|
57
|
+
#
|
58
|
+
# titleize is also aliased as as titlecase
|
59
|
+
#
|
60
|
+
# Examples
|
61
|
+
# "man from the boondocks".titleize #=> "Man From The Boondocks"
|
62
|
+
# "x-men: the last stand".titleize #=> "X Men: The Last Stand"
|
23
63
|
def titleize
|
24
64
|
Inflector.titleize(self)
|
25
65
|
end
|
26
66
|
alias_method :titlecase, :titleize
|
27
67
|
|
68
|
+
# The reverse of +camelize+. Makes an underscored form from the expression in the string.
|
69
|
+
#
|
70
|
+
# Changes '::' to '/' to convert namespaces to paths.
|
71
|
+
#
|
72
|
+
# Examples
|
73
|
+
# "ActiveRecord".underscore #=> "active_record"
|
74
|
+
# "ActiveRecord::Errors".underscore #=> active_record/errors
|
28
75
|
def underscore
|
29
76
|
Inflector.underscore(self)
|
30
77
|
end
|
31
78
|
|
79
|
+
# Replaces underscores with dashes in the string.
|
80
|
+
#
|
81
|
+
# Example
|
82
|
+
# "puni_puni" #=> "puni-puni"
|
32
83
|
def dasherize
|
33
84
|
Inflector.dasherize(self)
|
34
85
|
end
|
35
86
|
|
87
|
+
# Removes the module part from the expression in the string
|
88
|
+
#
|
89
|
+
# Examples
|
90
|
+
# "ActiveRecord::CoreExtensions::String::Inflections".demodulize #=> "Inflections"
|
91
|
+
# "Inflections".demodulize #=> "Inflections"
|
36
92
|
def demodulize
|
37
93
|
Inflector.demodulize(self)
|
38
94
|
end
|
39
95
|
|
96
|
+
# Create the name of a table like Rails does for models to table names. This method
|
97
|
+
# uses the pluralize method on the last word in the string.
|
98
|
+
#
|
99
|
+
# Examples
|
100
|
+
# "RawScaledScorer".tableize #=> "raw_scaled_scorers"
|
101
|
+
# "egg_and_ham".tableize #=> "egg_and_hams"
|
102
|
+
# "fancyCategory".tableize #=> "fancy_categories"
|
40
103
|
def tableize
|
41
104
|
Inflector.tableize(self)
|
42
105
|
end
|
43
106
|
|
107
|
+
# Create a class name from a table name like Rails does for table names to models.
|
108
|
+
# Note that this returns a string and not a Class. (To convert to an actual class
|
109
|
+
# follow classify with constantize.)
|
110
|
+
#
|
111
|
+
# Examples
|
112
|
+
# "egg_and_hams".classify #=> "EggAndHam"
|
113
|
+
# "post".classify #=> "Post"
|
44
114
|
def classify
|
45
115
|
Inflector.classify(self)
|
46
116
|
end
|
47
117
|
|
48
|
-
# Capitalizes the first word and turns underscores into spaces and strips _id
|
49
|
-
#
|
118
|
+
# Capitalizes the first word and turns underscores into spaces and strips _id.
|
119
|
+
# Like titleize, this is meant for creating pretty output.
|
120
|
+
#
|
121
|
+
# Examples
|
122
|
+
# "employee_salary" #=> "Employee salary"
|
123
|
+
# "author_id" #=> "Author"
|
50
124
|
def humanize
|
51
125
|
Inflector.humanize(self)
|
52
126
|
end
|
53
127
|
|
128
|
+
# Creates a foreign key name from a class name.
|
129
|
+
# +separate_class_name_and_id_with_underscore+ sets whether
|
130
|
+
# the method should put '_' between the name and 'id'.
|
131
|
+
#
|
132
|
+
# Examples
|
133
|
+
# "Message".foreign_key #=> "message_id"
|
134
|
+
# "Message".foreign_key(false) #=> "messageid"
|
135
|
+
# "Admin::Post".foreign_key #=> "post_id"
|
54
136
|
def foreign_key(separate_class_name_and_id_with_underscore = true)
|
55
137
|
Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
|
56
138
|
end
|
57
139
|
|
140
|
+
# Constantize tries to find a declared constant with the name specified
|
141
|
+
# in the string. It raises a NameError when the name is not in CamelCase
|
142
|
+
# or is not initialized.
|
143
|
+
#
|
144
|
+
# Examples
|
145
|
+
# "Module".constantize #=> Module
|
146
|
+
# "Class".constantize #=> Class
|
58
147
|
def constantize
|
59
148
|
Inflector.constantize(self)
|
60
149
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ActiveSupport #:nodoc:
|
2
|
+
module CoreExtensions #:nodoc:
|
3
|
+
module String #:nodoc:
|
4
|
+
# Define methods for handeling unicode data.
|
5
|
+
module Unicode
|
6
|
+
# +chars+ is a Unicode safe proxy for string methods. It creates and returns an instance of the
|
7
|
+
# ActiveSupport::Multibyte::Chars class which encapsulates the original string. A Unicode safe version of all
|
8
|
+
# the String methods are defined on this proxy class. Undefined methods are forwarded to String, so all of the
|
9
|
+
# string overrides can also be called through the +chars+ proxy.
|
10
|
+
#
|
11
|
+
# name = 'Claus Müller'
|
12
|
+
# name.reverse #=> "rell??M sualC"
|
13
|
+
# name.length #=> 13
|
14
|
+
#
|
15
|
+
# name.chars.reverse.to_s #=> "rellüM sualC"
|
16
|
+
# name.chars.length #=> 12
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# All the methods on the chars proxy which normally return a string will return a Chars object. This allows
|
20
|
+
# method chaining on the result of any of these methods.
|
21
|
+
#
|
22
|
+
# name.chars.reverse.length #=> 12
|
23
|
+
#
|
24
|
+
# The Char object tries to be as interchangeable with String objects as possible: sorting and comparing between
|
25
|
+
# String and Char work like expected. The bang! methods change the internal string representation in the Chars
|
26
|
+
# object. Interoperability problems can be resolved easily with a +to_s+ call.
|
27
|
+
#
|
28
|
+
# For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars and
|
29
|
+
# ActiveSupport::Multibyte::Handlers::UTF8Handler
|
30
|
+
def chars
|
31
|
+
ActiveSupport::Multibyte::Chars.new(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns true if the string has UTF-8 semantics (a String used for purely byte resources is unlikely to have
|
35
|
+
# them), returns false otherwise.
|
36
|
+
def is_utf8?
|
37
|
+
ActiveSupport::Multibyte::Handlers::UTF8Handler.consumes?(self)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -7,6 +7,6 @@ class Symbol
|
|
7
7
|
# # The same as people.select { |p| p.manager? }.collect { |p| p.salary }
|
8
8
|
# people.select(&:manager?).collect(&:salary)
|
9
9
|
def to_proc
|
10
|
-
Proc.new { |
|
10
|
+
Proc.new { |*args| args.shift.__send__(self, *args) }
|
11
11
|
end
|
12
12
|
end
|
@@ -3,8 +3,7 @@ module ActiveSupport #:nodoc:
|
|
3
3
|
module Time #:nodoc:
|
4
4
|
# Enables the use of time calculations within Time itself
|
5
5
|
module Calculations
|
6
|
-
def self.
|
7
|
-
super
|
6
|
+
def self.included(base) #:nodoc:
|
8
7
|
base.extend(ClassMethods)
|
9
8
|
end
|
10
9
|
|
@@ -26,7 +25,7 @@ module ActiveSupport #:nodoc:
|
|
26
25
|
|
27
26
|
# Seconds since midnight: Time.now.seconds_since_midnight
|
28
27
|
def seconds_since_midnight
|
29
|
-
self.
|
28
|
+
self.to_i - self.change(:hour => 0).to_i + (self.usec/1.0e+6)
|
30
29
|
end
|
31
30
|
|
32
31
|
# Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
|
@@ -57,13 +56,16 @@ module ActiveSupport #:nodoc:
|
|
57
56
|
# Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
|
58
57
|
# Do not use this method in combination with x.months, use months_ago instead!
|
59
58
|
def ago(seconds)
|
60
|
-
|
59
|
+
self.since(-seconds)
|
61
60
|
end
|
62
61
|
|
63
62
|
# Returns a new Time representing the time a number of seconds since the instance time, this is basically a wrapper around
|
64
63
|
#the Numeric extension. Do not use this method in combination with x.months, use months_since instead!
|
65
64
|
def since(seconds)
|
66
|
-
|
65
|
+
initial_dst = self.dst? ? 1 : 0
|
66
|
+
f = seconds.since(self)
|
67
|
+
final_dst = f.dst? ? 1 : 0
|
68
|
+
(seconds.abs >= 86400 && initial_dst != final_dst) ? f + (initial_dst - final_dst).hours : f
|
67
69
|
end
|
68
70
|
alias :in :since
|
69
71
|
|
@@ -13,8 +13,7 @@ module ActiveSupport #:nodoc:
|
|
13
13
|
:rfc822 => "%a, %d %b %Y %H:%M:%S %z"
|
14
14
|
}
|
15
15
|
|
16
|
-
def self.
|
17
|
-
super
|
16
|
+
def self.included(klass)
|
18
17
|
klass.send(:alias_method, :to_default_s, :to_s)
|
19
18
|
klass.send(:alias_method, :to_s, :to_formatted_s)
|
20
19
|
end
|
@@ -21,13 +21,44 @@ module Dependencies #:nodoc:
|
|
21
21
|
# Should we load files or require them?
|
22
22
|
mattr_accessor :mechanism
|
23
23
|
self.mechanism = :load
|
24
|
-
|
24
|
+
|
25
|
+
# The set of directories from which we may automatically load files. Files
|
26
|
+
# under these directories will be reloaded on each request in development mode,
|
27
|
+
# unless the directory also appears in load_once_paths.
|
28
|
+
mattr_accessor :load_paths
|
29
|
+
self.load_paths = []
|
30
|
+
|
31
|
+
# The set of directories from which automatically loaded constants are loaded
|
32
|
+
# only once. All directories in this set must also be present in +load_paths+.
|
33
|
+
mattr_accessor :load_once_paths
|
34
|
+
self.load_once_paths = []
|
35
|
+
|
36
|
+
# An array of qualified constant names that have been loaded. Adding a name to
|
37
|
+
# this array will cause it to be unloaded the next time Dependencies are cleared.
|
38
|
+
mattr_accessor :autoloaded_constants
|
39
|
+
self.autoloaded_constants = []
|
40
|
+
|
41
|
+
# An array of constant names that need to be unloaded on every request. Used
|
42
|
+
# to allow arbitrary constants to be marked for unloading.
|
43
|
+
mattr_accessor :explicitly_unloadable_constants
|
44
|
+
self.explicitly_unloadable_constants = []
|
45
|
+
|
46
|
+
# Set to true to enable logging of const_missing and file loads
|
47
|
+
mattr_accessor :log_activity
|
48
|
+
self.log_activity = false
|
49
|
+
|
50
|
+
# :nodoc:
|
51
|
+
# An internal stack used to record which constants are loaded by any block.
|
52
|
+
mattr_accessor :constant_watch_stack
|
53
|
+
self.constant_watch_stack = []
|
54
|
+
|
25
55
|
def load?
|
26
56
|
mechanism == :load
|
27
57
|
end
|
28
58
|
|
29
59
|
def depend_on(file_name, swallow_load_errors = false)
|
30
|
-
|
60
|
+
path = search_for_file(file_name)
|
61
|
+
require_or_load(path || file_name)
|
31
62
|
rescue LoadError
|
32
63
|
raise unless swallow_load_errors
|
33
64
|
end
|
@@ -37,36 +68,318 @@ module Dependencies #:nodoc:
|
|
37
68
|
end
|
38
69
|
|
39
70
|
def clear
|
71
|
+
log_call
|
40
72
|
loaded.clear
|
73
|
+
remove_unloadable_constants!
|
41
74
|
end
|
42
75
|
|
43
|
-
def require_or_load(file_name)
|
76
|
+
def require_or_load(file_name, const_path = nil)
|
77
|
+
log_call file_name, const_path
|
44
78
|
file_name = $1 if file_name =~ /^(.*)\.rb$/
|
45
|
-
|
79
|
+
expanded = File.expand_path(file_name)
|
80
|
+
return if loaded.include?(expanded)
|
46
81
|
|
47
82
|
# Record that we've seen this file *before* loading it to avoid an
|
48
83
|
# infinite loop with mutual dependencies.
|
49
|
-
loaded <<
|
50
|
-
|
84
|
+
loaded << expanded
|
85
|
+
|
51
86
|
if load?
|
87
|
+
log "loading #{file_name}"
|
52
88
|
begin
|
53
89
|
# Enable warnings iff this file has not been loaded before and
|
54
90
|
# warnings_on_first_load is set.
|
55
|
-
|
56
|
-
|
91
|
+
load_args = ["#{file_name}.rb"]
|
92
|
+
load_args << const_path unless const_path.nil?
|
93
|
+
|
94
|
+
if !warnings_on_first_load or history.include?(expanded)
|
95
|
+
result = load_file(*load_args)
|
57
96
|
else
|
58
|
-
enable_warnings {
|
97
|
+
enable_warnings { result = load_file(*load_args) }
|
59
98
|
end
|
60
|
-
rescue
|
61
|
-
loaded.delete
|
99
|
+
rescue Exception
|
100
|
+
loaded.delete expanded
|
62
101
|
raise
|
63
102
|
end
|
64
103
|
else
|
65
|
-
|
104
|
+
log "requiring #{file_name}"
|
105
|
+
result = require file_name
|
66
106
|
end
|
67
107
|
|
68
108
|
# Record history *after* loading so first load gets warnings.
|
69
|
-
history <<
|
109
|
+
history << expanded
|
110
|
+
return result
|
111
|
+
end
|
112
|
+
|
113
|
+
# Is the provided constant path defined?
|
114
|
+
def qualified_const_defined?(path)
|
115
|
+
raise NameError, "#{path.inspect} is not a valid constant name!" unless
|
116
|
+
/^(::)?([A-Z]\w*)(::[A-Z]\w*)*$/ =~ path
|
117
|
+
|
118
|
+
names = path.split('::')
|
119
|
+
names.shift if names.first.empty?
|
120
|
+
|
121
|
+
# We can't use defined? because it will invoke const_missing for the parent
|
122
|
+
# of the name we are checking.
|
123
|
+
names.inject(Object) do |mod, name|
|
124
|
+
return false unless mod.const_defined? name
|
125
|
+
mod.const_get name
|
126
|
+
end
|
127
|
+
return true
|
128
|
+
end
|
129
|
+
|
130
|
+
# Given +path+, a filesystem path to a ruby file, return an array of constant
|
131
|
+
# paths which would cause Dependencies to attempt to load this file.
|
132
|
+
#
|
133
|
+
def loadable_constants_for_path(path, bases = load_paths)
|
134
|
+
path = $1 if path =~ /\A(.*)\.rb\Z/
|
135
|
+
expanded_path = File.expand_path(path)
|
136
|
+
|
137
|
+
bases.collect do |root|
|
138
|
+
expanded_root = File.expand_path(root)
|
139
|
+
next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path
|
140
|
+
|
141
|
+
nesting = expanded_path[(expanded_root.size)..-1]
|
142
|
+
nesting = nesting[1..-1] if nesting && nesting[0] == ?/
|
143
|
+
next if nesting.blank?
|
144
|
+
|
145
|
+
[
|
146
|
+
nesting.camelize,
|
147
|
+
# Special case: application.rb might define ApplicationControlller.
|
148
|
+
('ApplicationController' if nesting == 'application')
|
149
|
+
]
|
150
|
+
end.flatten.compact.uniq
|
151
|
+
end
|
152
|
+
|
153
|
+
# Search for a file in load_paths matching the provided suffix.
|
154
|
+
def search_for_file(path_suffix)
|
155
|
+
path_suffix = path_suffix + '.rb' unless path_suffix.ends_with? '.rb'
|
156
|
+
load_paths.each do |root|
|
157
|
+
path = File.join(root, path_suffix)
|
158
|
+
return path if File.file? path
|
159
|
+
end
|
160
|
+
nil # Gee, I sure wish we had first_match ;-)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Does the provided path_suffix correspond to an autoloadable module?
|
164
|
+
# Instead of returning a boolean, the autoload base for this module is returned.
|
165
|
+
def autoloadable_module?(path_suffix)
|
166
|
+
load_paths.each do |load_path|
|
167
|
+
return load_path if File.directory? File.join(load_path, path_suffix)
|
168
|
+
end
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
|
172
|
+
def load_once_path?(path)
|
173
|
+
load_once_paths.any? { |base| path.starts_with? base }
|
174
|
+
end
|
175
|
+
|
176
|
+
# Attempt to autoload the provided module name by searching for a directory
|
177
|
+
# matching the expect path suffix. If found, the module is created and assigned
|
178
|
+
# to +into+'s constants with the name +const_name+. Provided that the directory
|
179
|
+
# was loaded from a reloadable base path, it is added to the set of constants
|
180
|
+
# that are to be unloaded.
|
181
|
+
def autoload_module!(into, const_name, qualified_name, path_suffix)
|
182
|
+
return nil unless base_path = autoloadable_module?(path_suffix)
|
183
|
+
mod = Module.new
|
184
|
+
into.const_set const_name, mod
|
185
|
+
autoloaded_constants << qualified_name unless load_once_paths.include?(base_path)
|
186
|
+
return mod
|
187
|
+
end
|
188
|
+
|
189
|
+
# Load the file at the provided path. +const_paths+ is a set of qualified
|
190
|
+
# constant names. When loading the file, Dependencies will watch for the
|
191
|
+
# addition of these constants. Each that is defined will be marked as
|
192
|
+
# autoloaded, and will be removed when Dependencies.clear is next called.
|
193
|
+
#
|
194
|
+
# If the second parameter is left off, then Dependencies will construct a set
|
195
|
+
# of names that the file at +path+ may define. See
|
196
|
+
# +loadable_constants_for_path+ for more details.
|
197
|
+
def load_file(path, const_paths = loadable_constants_for_path(path))
|
198
|
+
log_call path, const_paths
|
199
|
+
const_paths = [const_paths].compact unless const_paths.is_a? Array
|
200
|
+
parent_paths = const_paths.collect { |const_path| /(.*)::[^:]+\Z/ =~ const_path ? $1 : :Object }
|
201
|
+
|
202
|
+
result = nil
|
203
|
+
newly_defined_paths = new_constants_in(*parent_paths) do
|
204
|
+
result = load_without_new_constant_marking path
|
205
|
+
end
|
206
|
+
|
207
|
+
autoloaded_constants.concat newly_defined_paths unless load_once_path?(path)
|
208
|
+
autoloaded_constants.uniq!
|
209
|
+
log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
|
210
|
+
return result
|
211
|
+
end
|
212
|
+
|
213
|
+
# Return the constant path for the provided parent and constant name.
|
214
|
+
def qualified_name_for(mod, name)
|
215
|
+
mod_name = to_constant_name mod
|
216
|
+
(%w(Object Kernel).include? mod_name) ? name.to_s : "#{mod_name}::#{name}"
|
217
|
+
end
|
218
|
+
|
219
|
+
# Load the constant named +const_name+ which is missing from +from_mod+. If
|
220
|
+
# it is not possible to laod the constant into from_mod, try its parent module
|
221
|
+
# using const_missing.
|
222
|
+
def load_missing_constant(from_mod, const_name)
|
223
|
+
log_call from_mod, const_name
|
224
|
+
if from_mod == Kernel
|
225
|
+
if ::Object.const_defined?(const_name)
|
226
|
+
log "Returning Object::#{const_name} for Kernel::#{const_name}"
|
227
|
+
return ::Object.const_get(const_name)
|
228
|
+
else
|
229
|
+
log "Substituting Object for Kernel"
|
230
|
+
from_mod = Object
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# If we have an anonymous module, all we can do is attempt to load from Object.
|
235
|
+
from_mod = Object if from_mod.name.empty?
|
236
|
+
|
237
|
+
unless qualified_const_defined?(from_mod.name) && from_mod.name.constantize.object_id == from_mod.object_id
|
238
|
+
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
|
239
|
+
end
|
240
|
+
|
241
|
+
raise ArgumentError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name)
|
242
|
+
|
243
|
+
qualified_name = qualified_name_for from_mod, const_name
|
244
|
+
path_suffix = qualified_name.underscore
|
245
|
+
name_error = NameError.new("uninitialized constant #{qualified_name}")
|
246
|
+
|
247
|
+
file_path = search_for_file(path_suffix)
|
248
|
+
if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load
|
249
|
+
require_or_load file_path
|
250
|
+
raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless from_mod.const_defined?(const_name)
|
251
|
+
return from_mod.const_get(const_name)
|
252
|
+
elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
|
253
|
+
return mod
|
254
|
+
elsif (parent = from_mod.parent) && parent != from_mod &&
|
255
|
+
! from_mod.parents.any? { |p| p.const_defined?(const_name) }
|
256
|
+
# If our parents do not have a constant named +const_name+ then we are free
|
257
|
+
# to attempt to load upwards. If they do have such a constant, then this
|
258
|
+
# const_missing must be due to from_mod::const_name, which should not
|
259
|
+
# return constants from from_mod's parents.
|
260
|
+
begin
|
261
|
+
return parent.const_missing(const_name)
|
262
|
+
rescue NameError => e
|
263
|
+
raise unless e.missing_name? qualified_name_for(parent, const_name)
|
264
|
+
raise name_error
|
265
|
+
end
|
266
|
+
else
|
267
|
+
raise name_error
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Remove the constants that have been autoloaded, and those that have been
|
272
|
+
# marked for unloading.
|
273
|
+
def remove_unloadable_constants!
|
274
|
+
autoloaded_constants.each { |const| remove_constant const }
|
275
|
+
autoloaded_constants.clear
|
276
|
+
explicitly_unloadable_constants.each { |const| remove_constant const }
|
277
|
+
end
|
278
|
+
|
279
|
+
# Determine if the given constant has been automatically loaded.
|
280
|
+
def autoloaded?(desc)
|
281
|
+
# No name => anonymous module.
|
282
|
+
return false if desc.is_a?(Module) && desc.name.blank?
|
283
|
+
name = to_constant_name desc
|
284
|
+
return false unless qualified_const_defined? name
|
285
|
+
return autoloaded_constants.include?(name)
|
286
|
+
end
|
287
|
+
|
288
|
+
# Will the provided constant descriptor be unloaded?
|
289
|
+
def will_unload?(const_desc)
|
290
|
+
autoloaded?(desc) ||
|
291
|
+
explicitly_unloadable_constants.include?(to_constant_name(const_desc))
|
292
|
+
end
|
293
|
+
|
294
|
+
# Mark the provided constant name for unloading. This constant will be
|
295
|
+
# unloaded on each request, not just the next one.
|
296
|
+
def mark_for_unload(const_desc)
|
297
|
+
name = to_constant_name const_desc
|
298
|
+
if explicitly_unloadable_constants.include? name
|
299
|
+
return false
|
300
|
+
else
|
301
|
+
explicitly_unloadable_constants << name
|
302
|
+
return true
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Run the provided block and detect the new constants that were loaded during
|
307
|
+
# its execution. Constants may only be regarded as 'new' once -- so if the
|
308
|
+
# block calls +new_constants_in+ again, then the constants defined within the
|
309
|
+
# inner call will not be reported in this one.
|
310
|
+
#
|
311
|
+
# If the provided block does not run to completion, and instead raises an
|
312
|
+
# exception, any new constants are regarded as being only partially defined
|
313
|
+
# and will be removed immediately.
|
314
|
+
def new_constants_in(*descs)
|
315
|
+
log_call(*descs)
|
316
|
+
|
317
|
+
# Build the watch frames. Each frame is a tuple of
|
318
|
+
# [module_name_as_string, constants_defined_elsewhere]
|
319
|
+
watch_frames = descs.collect do |desc|
|
320
|
+
if desc.is_a? Module
|
321
|
+
mod_name = desc.name
|
322
|
+
initial_constants = desc.constants
|
323
|
+
elsif desc.is_a?(String) || desc.is_a?(Symbol)
|
324
|
+
mod_name = desc.to_s
|
325
|
+
|
326
|
+
# Handle the case where the module has yet to be defined.
|
327
|
+
initial_constants = if qualified_const_defined?(mod_name)
|
328
|
+
mod_name.constantize.constants
|
329
|
+
else
|
330
|
+
[]
|
331
|
+
end
|
332
|
+
else
|
333
|
+
raise Argument, "#{desc.inspect} does not describe a module!"
|
334
|
+
end
|
335
|
+
|
336
|
+
[mod_name, initial_constants]
|
337
|
+
end
|
338
|
+
|
339
|
+
constant_watch_stack.concat watch_frames
|
340
|
+
|
341
|
+
aborting = true
|
342
|
+
begin
|
343
|
+
yield # Now yield to the code that is to define new constants.
|
344
|
+
aborting = false
|
345
|
+
ensure
|
346
|
+
# Find the new constants.
|
347
|
+
new_constants = watch_frames.collect do |mod_name, prior_constants|
|
348
|
+
# Module still doesn't exist? Treat it as if it has no constants.
|
349
|
+
next [] unless qualified_const_defined?(mod_name)
|
350
|
+
|
351
|
+
mod = mod_name.constantize
|
352
|
+
next [] unless mod.is_a? Module
|
353
|
+
new_constants = mod.constants - prior_constants
|
354
|
+
|
355
|
+
# Make sure no other frames takes credit for these constants.
|
356
|
+
constant_watch_stack.each do |frame_name, constants|
|
357
|
+
constants.concat new_constants if frame_name == mod_name
|
358
|
+
end
|
359
|
+
|
360
|
+
new_constants.collect do |suffix|
|
361
|
+
mod_name == "Object" ? suffix : "#{mod_name}::#{suffix}"
|
362
|
+
end
|
363
|
+
end.flatten
|
364
|
+
|
365
|
+
log "New constants: #{new_constants * ', '}"
|
366
|
+
|
367
|
+
if aborting
|
368
|
+
log "Error during loading, removing partially loaded constants "
|
369
|
+
new_constants.each { |name| remove_constant name }
|
370
|
+
new_constants.clear
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
return new_constants
|
375
|
+
ensure
|
376
|
+
# Remove the stack frames that we added.
|
377
|
+
if defined?(watch_frames) && ! watch_frames.empty?
|
378
|
+
frame_ids = watch_frames.collect(&:object_id)
|
379
|
+
constant_watch_stack.delete_if do |watch_frame|
|
380
|
+
frame_ids.include? watch_frame.object_id
|
381
|
+
end
|
382
|
+
end
|
70
383
|
end
|
71
384
|
|
72
385
|
class LoadingModule
|
@@ -79,6 +392,51 @@ module Dependencies #:nodoc:
|
|
79
392
|
end
|
80
393
|
end
|
81
394
|
end
|
395
|
+
|
396
|
+
protected
|
397
|
+
|
398
|
+
# Convert the provided const desc to a qualified constant name (as a string).
|
399
|
+
# A module, class, symbol, or string may be provided.
|
400
|
+
def to_constant_name(desc)
|
401
|
+
name = case desc
|
402
|
+
when String then desc.starts_with?('::') ? desc[2..-1] : desc
|
403
|
+
when Symbol then desc.to_s
|
404
|
+
when Module
|
405
|
+
raise ArgumentError, "Anonymous modules have no name to be referenced by" if desc.name.blank?
|
406
|
+
desc.name
|
407
|
+
else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def remove_constant(const)
|
412
|
+
return false unless qualified_const_defined? const
|
413
|
+
|
414
|
+
const = $1 if /\A::(.*)\Z/ =~ const.to_s
|
415
|
+
names = const.split('::')
|
416
|
+
if names.size == 1 # It's under Object
|
417
|
+
parent = Object
|
418
|
+
else
|
419
|
+
parent = (names[0..-2] * '::').constantize
|
420
|
+
end
|
421
|
+
|
422
|
+
log "removing constant #{const}"
|
423
|
+
parent.send :remove_const, names.last
|
424
|
+
return true
|
425
|
+
end
|
426
|
+
|
427
|
+
def log_call(*args)
|
428
|
+
arg_str = args.collect(&:inspect) * ', '
|
429
|
+
/in `([a-z_\?\!]+)'/ =~ caller(1).first
|
430
|
+
selector = $1 || '<unknown>'
|
431
|
+
log "called #{selector}(#{arg_str})"
|
432
|
+
end
|
433
|
+
|
434
|
+
def log(msg)
|
435
|
+
if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER && log_activity
|
436
|
+
RAILS_DEFAULT_LOGGER.debug "Dependencies: #{msg}"
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
82
440
|
end
|
83
441
|
|
84
442
|
Object.send(:define_method, :require_or_load) { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load)
|
@@ -92,37 +450,13 @@ class Module #:nodoc:
|
|
92
450
|
# Use const_missing to autoload associations so we don't have to
|
93
451
|
# require_association when using single-table inheritance.
|
94
452
|
def const_missing(class_id)
|
95
|
-
|
96
|
-
file_path = as_load_path.empty? ? file_name : "#{as_load_path}/#{file_name}"
|
97
|
-
begin
|
98
|
-
require_dependency(file_path)
|
99
|
-
brief_name = self == Object ? '' : "#{name}::"
|
100
|
-
raise NameError.new("uninitialized constant #{brief_name}#{class_id}") unless const_defined?(class_id)
|
101
|
-
return const_get(class_id)
|
102
|
-
rescue MissingSourceFile => e
|
103
|
-
# Re-raise the error if it does not concern the file we were trying to load.
|
104
|
-
raise unless e.is_missing? file_path
|
105
|
-
|
106
|
-
# Look for a directory in the load path that we ought to load.
|
107
|
-
if $LOAD_PATH.any? { |base| File.directory? "#{base}/#{file_path}" }
|
108
|
-
mod = Module.new
|
109
|
-
const_set class_id, mod # Create the new module
|
110
|
-
return mod
|
111
|
-
end
|
112
|
-
|
113
|
-
# Attempt to access the name from the parent, unless we don't have a valid
|
114
|
-
# parent, or the constant is already defined in the parent. If the latter
|
115
|
-
# is the case, then we are being queried via self::class_id, and we should
|
116
|
-
# avoid returning the constant from the parent if possible.
|
117
|
-
if parent && parent != self && ! parents.any? { |p| p.const_defined?(class_id) }
|
118
|
-
suppress(NameError) do
|
119
|
-
return parent.send(:const_missing, class_id)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
raise NameError.new("uninitialized constant #{class_id}").copy_blame!(e)
|
124
|
-
end
|
453
|
+
Dependencies.load_missing_constant self, class_id
|
125
454
|
end
|
455
|
+
|
456
|
+
def unloadable(const_desc = self)
|
457
|
+
super(const_desc)
|
458
|
+
end
|
459
|
+
|
126
460
|
end
|
127
461
|
|
128
462
|
class Class
|
@@ -130,25 +464,58 @@ class Class
|
|
130
464
|
if [Object, Kernel].include?(self) || parent == self
|
131
465
|
super
|
132
466
|
else
|
133
|
-
|
467
|
+
begin
|
468
|
+
begin
|
469
|
+
Dependencies.load_missing_constant self, class_id
|
470
|
+
rescue NameError
|
471
|
+
parent.send :const_missing, class_id
|
472
|
+
end
|
473
|
+
rescue NameError => e
|
474
|
+
# Make sure that the name we are missing is the one that caused the error
|
475
|
+
parent_qualified_name = Dependencies.qualified_name_for parent, class_id
|
476
|
+
raise unless e.missing_name? parent_qualified_name
|
477
|
+
qualified_name = Dependencies.qualified_name_for self, class_id
|
478
|
+
raise NameError.new("uninitialized constant #{qualified_name}").copy_blame!(e)
|
479
|
+
end
|
134
480
|
end
|
135
481
|
end
|
136
482
|
end
|
137
483
|
|
138
484
|
class Object #:nodoc:
|
485
|
+
|
486
|
+
alias_method :load_without_new_constant_marking, :load
|
487
|
+
|
139
488
|
def load(file, *extras)
|
140
|
-
super(file, *extras)
|
141
|
-
rescue
|
489
|
+
Dependencies.new_constants_in(Object) { super(file, *extras) }
|
490
|
+
rescue Exception => exception # errors from loading file
|
142
491
|
exception.blame_file! file
|
143
492
|
raise
|
144
493
|
end
|
145
494
|
|
146
495
|
def require(file, *extras)
|
147
|
-
super(file, *extras)
|
148
|
-
rescue
|
496
|
+
Dependencies.new_constants_in(Object) { super(file, *extras) }
|
497
|
+
rescue Exception => exception # errors from required file
|
149
498
|
exception.blame_file! file
|
150
499
|
raise
|
151
500
|
end
|
501
|
+
|
502
|
+
# Mark the given constant as unloadable. Unloadable constants are removed each
|
503
|
+
# time dependencies are cleared.
|
504
|
+
#
|
505
|
+
# Note that marking a constant for unloading need only be done once. Setup
|
506
|
+
# or init scripts may list each unloadable constant that may need unloading;
|
507
|
+
# each constant will be removed for every subsequent clear, as opposed to for
|
508
|
+
# the first clear.
|
509
|
+
#
|
510
|
+
# The provided constant descriptor may be a (non-anonymous) module or class,
|
511
|
+
# or a qualified constant name as a string or symbol.
|
512
|
+
#
|
513
|
+
# Returns true if the constant was not previously marked for unloading, false
|
514
|
+
# otherwise.
|
515
|
+
def unloadable(const_desc)
|
516
|
+
Dependencies.mark_for_unload const_desc
|
517
|
+
end
|
518
|
+
|
152
519
|
end
|
153
520
|
|
154
521
|
# Add file-blaming to exceptions
|
@@ -163,11 +530,11 @@ class Exception #:nodoc:
|
|
163
530
|
|
164
531
|
def describe_blame
|
165
532
|
return nil if blamed_files.empty?
|
166
|
-
"This error
|
533
|
+
"This error occurred while loading the following files:\n #{blamed_files.join "\n "}"
|
167
534
|
end
|
168
535
|
|
169
536
|
def copy_blame!(exc)
|
170
537
|
@blamed_files = exc.blamed_files.clone
|
171
538
|
self
|
172
539
|
end
|
173
|
-
end
|
540
|
+
end
|