cassandra_mapper 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +98 -0
- data/Rakefile.rb +11 -0
- data/lib/cassandra_mapper.rb +5 -0
- data/lib/cassandra_mapper/base.rb +19 -0
- data/lib/cassandra_mapper/connection.rb +9 -0
- data/lib/cassandra_mapper/core_ext/array/extract_options.rb +29 -0
- data/lib/cassandra_mapper/core_ext/array/wrap.rb +22 -0
- data/lib/cassandra_mapper/core_ext/class/inheritable_attributes.rb +232 -0
- data/lib/cassandra_mapper/core_ext/kernel/reporting.rb +62 -0
- data/lib/cassandra_mapper/core_ext/kernel/singleton_class.rb +13 -0
- data/lib/cassandra_mapper/core_ext/module/aliasing.rb +70 -0
- data/lib/cassandra_mapper/core_ext/module/attribute_accessors.rb +66 -0
- data/lib/cassandra_mapper/core_ext/object/duplicable.rb +65 -0
- data/lib/cassandra_mapper/core_ext/string/inflections.rb +160 -0
- data/lib/cassandra_mapper/core_ext/string/multibyte.rb +72 -0
- data/lib/cassandra_mapper/exceptions.rb +10 -0
- data/lib/cassandra_mapper/identity.rb +29 -0
- data/lib/cassandra_mapper/indexing.rb +465 -0
- data/lib/cassandra_mapper/observable.rb +36 -0
- data/lib/cassandra_mapper/persistence.rb +309 -0
- data/lib/cassandra_mapper/support/callbacks.rb +136 -0
- data/lib/cassandra_mapper/support/concern.rb +31 -0
- data/lib/cassandra_mapper/support/dependencies.rb +60 -0
- data/lib/cassandra_mapper/support/descendants_tracker.rb +41 -0
- data/lib/cassandra_mapper/support/inflections.rb +58 -0
- data/lib/cassandra_mapper/support/inflector.rb +7 -0
- data/lib/cassandra_mapper/support/inflector/inflections.rb +213 -0
- data/lib/cassandra_mapper/support/inflector/methods.rb +143 -0
- data/lib/cassandra_mapper/support/inflector/transliterate.rb +99 -0
- data/lib/cassandra_mapper/support/multibyte.rb +46 -0
- data/lib/cassandra_mapper/support/multibyte/utils.rb +62 -0
- data/lib/cassandra_mapper/support/observing.rb +218 -0
- data/lib/cassandra_mapper/support/support_callbacks.rb +593 -0
- data/test/test_helper.rb +11 -0
- data/test/unit/callbacks_test.rb +100 -0
- data/test/unit/identity_test.rb +51 -0
- data/test/unit/indexing_test.rb +406 -0
- data/test/unit/observer_test.rb +56 -0
- data/test/unit/persistence_test.rb +561 -0
- metadata +192 -0
data/README.rdoc
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
= CassandraMapper: Easily build classes for working with Cassandra
|
2
|
+
|
3
|
+
+CassandraMapper+ uses the features and semantics of +SimpleMapper+ to make
|
4
|
+
working with your Cassandra schema productive and expressive.
|
5
|
+
|
6
|
+
Build your Cassandra-fronting model classes with +CassandraMapper+, using the
|
7
|
+
same straightforward semantics of +SimpleMapper+. +CassandraMapper+ adds in
|
8
|
+
basic connection management (very basic, and easily customized) and persistence
|
9
|
+
logic for inserting/updating via the Thrift client.
|
10
|
+
|
11
|
+
class Animal < CassandraMapper::Base
|
12
|
+
# specify the name of the column family fronted by this class
|
13
|
+
column_family 'Animals'
|
14
|
+
|
15
|
+
# indicate which attribute should serve as the key for identification
|
16
|
+
key :species
|
17
|
+
|
18
|
+
# specify the attributes you expect ala SimpleMapper
|
19
|
+
maps :species, :type => :string
|
20
|
+
maps :name, :type => :string
|
21
|
+
|
22
|
+
# define a custom type to define the domain of a given attribute
|
23
|
+
module DietaryPreference
|
24
|
+
DIETS = [:herbivore, :carnivore, :omnivore].inject({}) {|h, v| h[v] = v.to_s; h}
|
25
|
+
# encode should convert a "native" value (as you would work with it in Ruby)
|
26
|
+
# to the Cassandra storage format at it should be passed to Thrift.
|
27
|
+
def self.encode(value)
|
28
|
+
raise Exception unless value = DIETS[value]
|
29
|
+
value
|
30
|
+
end
|
31
|
+
# decode should convert a Cassandra/Thrift value to a "native" value (the inverse
|
32
|
+
# of encode)
|
33
|
+
def self.decode(value)
|
34
|
+
raise Exception unless DIETS.has_key?( key = value.to_sym )
|
35
|
+
key
|
36
|
+
end
|
37
|
+
# let's default to herbivore, for a kinder, gentler world
|
38
|
+
def self.default
|
39
|
+
:herbivore
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Anything that has decode/encode/default can serve as a type,
|
44
|
+
# without being registered with the general symbol-lookup
|
45
|
+
# type registry.
|
46
|
+
# The :from_type default will mean the default value for this
|
47
|
+
# attribute (when it is left undefined) will come from the type.
|
48
|
+
maps :diet, :type => DietaryPreference, :default => :from_type
|
49
|
+
end
|
50
|
+
|
51
|
+
With a class defined, you can work with these objects as you would intuitively
|
52
|
+
expect (though you'll need to see connection management topics to get this to work).
|
53
|
+
|
54
|
+
# now let's create some animals, in order of ascending stupidity
|
55
|
+
deer = Animal.new(:species => 'odocoileus virginianus',
|
56
|
+
:name => 'White-tailed Deer)
|
57
|
+
deer.save
|
58
|
+
gull = Animal.new(:species => 'larus occidentalis',
|
59
|
+
:name => 'Seagull',
|
60
|
+
:diet => :carnivore)
|
61
|
+
gull.save
|
62
|
+
human = Animal.new(:species => 'homo sapiens',
|
63
|
+
:name => 'Human',
|
64
|
+
:diet => :omnivore)
|
65
|
+
human.save
|
66
|
+
# and let's fetch 'em back. Note that we didn't need to specify the :diet for the deer.
|
67
|
+
# This should return [:herbivore, :omnivore, :carnivore], though not necessarily in that order
|
68
|
+
Animal.find([human, deer, gull].collect {|animal| animal.species}).collect {|a| a.diet}
|
69
|
+
|
70
|
+
= Connection Management
|
71
|
+
|
72
|
+
As stated above, connection management is quite simple. Bare bones, in fact.
|
73
|
+
|
74
|
+
At present, +CassandraMapper+ expects you to manage your connections to +Cassandra+
|
75
|
+
yourself. There are a variety of reasons for this. The most important one is
|
76
|
+
that the lack of transactional isolation, or even "session" isolation, and the
|
77
|
+
whole eventually-consistent paradigm, effectively deprecate the idea of having all
|
78
|
+
steps in a business transaction take place on the same connection. If you're using
|
79
|
+
Cassandra, chances are you're interested in serious scaling of writes or reads or
|
80
|
+
both. This is best achieved by managing connections yourself in a way that maximizes
|
81
|
+
scalability/throughput for your use case.
|
82
|
+
|
83
|
+
It is likely that connection management will be introduced in more sophisticated form
|
84
|
+
in the reasonably near future, but that's not the most pressing priority. So, for
|
85
|
+
the time being, connections are at the class level (like in +ActiveRecord+) and need
|
86
|
+
to be explicitly assigned.
|
87
|
+
|
88
|
+
Thus, for the +Animal+ set/get examples above to truly work, you would need to
|
89
|
+
get an instance of the +Cassandra+ thrift client (+Cassandra.new(...)+), and
|
90
|
+
you would need to assign it to the +Animal+ class.
|
91
|
+
|
92
|
+
# load up the Cassandra client
|
93
|
+
require 'cassandra'
|
94
|
+
# get a connection and assign it to the class.
|
95
|
+
Animal.connection = Cassandra.new('YourKeyspace', '127.0.0.1:9160')
|
96
|
+
|
97
|
+
A near-term improvement will allow per-object specification of the connection,
|
98
|
+
for more flexible management.
|
data/Rakefile.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'simple_mapper'
|
2
|
+
class CassandraMapper::Base
|
3
|
+
include SimpleMapper::Attributes
|
4
|
+
|
5
|
+
require 'cassandra_mapper/identity'
|
6
|
+
include CassandraMapper::Identity
|
7
|
+
|
8
|
+
require 'cassandra_mapper/persistence'
|
9
|
+
include CassandraMapper::Persistence
|
10
|
+
|
11
|
+
require 'cassandra_mapper/connection'
|
12
|
+
include CassandraMapper::Connection
|
13
|
+
|
14
|
+
require 'cassandra_mapper/observable'
|
15
|
+
include CassandraMapper::Observable
|
16
|
+
|
17
|
+
require 'cassandra_mapper/indexing'
|
18
|
+
include CassandraMapper::Indexing
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Hash
|
2
|
+
# By default, only instances of Hash itself are extractable.
|
3
|
+
# Subclasses of Hash may implement this method and return
|
4
|
+
# true to declare themselves as extractable. If a Hash
|
5
|
+
# is extractable, Array#extract_options! pops it from
|
6
|
+
# the Array when it is the last element of the Array.
|
7
|
+
def extractable_options?
|
8
|
+
instance_of?(Hash)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Array
|
13
|
+
# Extracts options from a set of arguments. Removes and returns the last
|
14
|
+
# element in the array if it's a hash, otherwise returns a blank hash.
|
15
|
+
#
|
16
|
+
# def options(*args)
|
17
|
+
# args.extract_options!
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# options(1, 2) # => {}
|
21
|
+
# options(1, 2, :a => :b) # => {:a=>:b}
|
22
|
+
def extract_options!
|
23
|
+
if last.is_a?(Hash) && last.extractable_options?
|
24
|
+
pop
|
25
|
+
else
|
26
|
+
{}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Array
|
2
|
+
# Wraps the object in an Array unless it's an Array. Converts the
|
3
|
+
# object to an Array using #to_ary if it implements that.
|
4
|
+
#
|
5
|
+
# It differs with Array() in that it does not call +to_a+ on
|
6
|
+
# the argument:
|
7
|
+
#
|
8
|
+
# Array(:foo => :bar) # => [[:foo, :bar]]
|
9
|
+
# Array.wrap(:foo => :bar) # => [{:foo => :bar}]
|
10
|
+
#
|
11
|
+
# Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8
|
12
|
+
# Array.wrap("foo\nbar") # => ["foo\nbar"]
|
13
|
+
def self.wrap(object)
|
14
|
+
if object.nil?
|
15
|
+
[]
|
16
|
+
elsif object.respond_to?(:to_ary)
|
17
|
+
object.to_ary
|
18
|
+
else
|
19
|
+
[object]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'cassandra_mapper/core_ext/object/duplicable'
|
2
|
+
require 'cassandra_mapper/core_ext/array/extract_options'
|
3
|
+
|
4
|
+
# Retain for backward compatibility. Methods are now included in Class.
|
5
|
+
module ClassInheritableAttributes # :nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
# Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
|
9
|
+
# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
|
10
|
+
# to, for example, an array without those additions being shared with either their parent, siblings, or
|
11
|
+
# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
|
12
|
+
#
|
13
|
+
# The copies of inheritable parent attributes are added to subclasses when they are created, via the
|
14
|
+
# +inherited+ hook.
|
15
|
+
class Class # :nodoc:
|
16
|
+
def class_inheritable_reader(*syms)
|
17
|
+
options = syms.extract_options!
|
18
|
+
syms.each do |sym|
|
19
|
+
next if sym.is_a?(Hash)
|
20
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
21
|
+
def self.#{sym} # def self.after_add
|
22
|
+
read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:after_add)
|
23
|
+
end # end
|
24
|
+
#
|
25
|
+
#{" #
|
26
|
+
def #{sym} # def after_add
|
27
|
+
self.class.#{sym} # self.class.after_add
|
28
|
+
end # end
|
29
|
+
" unless options[:instance_reader] == false } # # the reader above is generated unless options[:instance_reader] == false
|
30
|
+
EOS
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def class_inheritable_writer(*syms)
|
35
|
+
options = syms.extract_options!
|
36
|
+
syms.each do |sym|
|
37
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
38
|
+
def self.#{sym}=(obj) # def self.color=(obj)
|
39
|
+
write_inheritable_attribute(:#{sym}, obj) # write_inheritable_attribute(:color, obj)
|
40
|
+
end # end
|
41
|
+
#
|
42
|
+
#{" #
|
43
|
+
def #{sym}=(obj) # def color=(obj)
|
44
|
+
self.class.#{sym} = obj # self.class.color = obj
|
45
|
+
end # end
|
46
|
+
" unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
|
47
|
+
EOS
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def class_inheritable_array_writer(*syms)
|
52
|
+
options = syms.extract_options!
|
53
|
+
syms.each do |sym|
|
54
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
55
|
+
def self.#{sym}=(obj) # def self.levels=(obj)
|
56
|
+
write_inheritable_array(:#{sym}, obj) # write_inheritable_array(:levels, obj)
|
57
|
+
end # end
|
58
|
+
#
|
59
|
+
#{" #
|
60
|
+
def #{sym}=(obj) # def levels=(obj)
|
61
|
+
self.class.#{sym} = obj # self.class.levels = obj
|
62
|
+
end # end
|
63
|
+
" unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
|
64
|
+
EOS
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def class_inheritable_hash_writer(*syms)
|
69
|
+
options = syms.extract_options!
|
70
|
+
syms.each do |sym|
|
71
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
72
|
+
def self.#{sym}=(obj) # def self.nicknames=(obj)
|
73
|
+
write_inheritable_hash(:#{sym}, obj) # write_inheritable_hash(:nicknames, obj)
|
74
|
+
end # end
|
75
|
+
#
|
76
|
+
#{" #
|
77
|
+
def #{sym}=(obj) # def nicknames=(obj)
|
78
|
+
self.class.#{sym} = obj # self.class.nicknames = obj
|
79
|
+
end # end
|
80
|
+
" unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
|
81
|
+
EOS
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def class_inheritable_accessor(*syms)
|
86
|
+
class_inheritable_reader(*syms)
|
87
|
+
class_inheritable_writer(*syms)
|
88
|
+
end
|
89
|
+
|
90
|
+
def class_inheritable_array(*syms)
|
91
|
+
class_inheritable_reader(*syms)
|
92
|
+
class_inheritable_array_writer(*syms)
|
93
|
+
end
|
94
|
+
|
95
|
+
def class_inheritable_hash(*syms)
|
96
|
+
class_inheritable_reader(*syms)
|
97
|
+
class_inheritable_hash_writer(*syms)
|
98
|
+
end
|
99
|
+
|
100
|
+
def inheritable_attributes
|
101
|
+
@inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
|
102
|
+
end
|
103
|
+
|
104
|
+
def write_inheritable_attribute(key, value)
|
105
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
106
|
+
@inheritable_attributes = {}
|
107
|
+
end
|
108
|
+
inheritable_attributes[key] = value
|
109
|
+
end
|
110
|
+
|
111
|
+
def write_inheritable_array(key, elements)
|
112
|
+
write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
|
113
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
|
114
|
+
end
|
115
|
+
|
116
|
+
def write_inheritable_hash(key, hash)
|
117
|
+
write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
|
118
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
|
119
|
+
end
|
120
|
+
|
121
|
+
def read_inheritable_attribute(key)
|
122
|
+
inheritable_attributes[key]
|
123
|
+
end
|
124
|
+
|
125
|
+
def reset_inheritable_attributes
|
126
|
+
@inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
# Prevent this constant from being created multiple times
|
131
|
+
EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
|
132
|
+
|
133
|
+
def inherited_with_inheritable_attributes(child)
|
134
|
+
inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
|
135
|
+
|
136
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
137
|
+
new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
138
|
+
else
|
139
|
+
new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
|
140
|
+
memo.update(key => value.duplicable? ? value.dup : value)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
|
145
|
+
end
|
146
|
+
|
147
|
+
alias inherited_without_inheritable_attributes inherited
|
148
|
+
alias inherited inherited_with_inheritable_attributes
|
149
|
+
end
|
150
|
+
|
151
|
+
class Class
|
152
|
+
# Defines class-level inheritable attribute reader. Attributes are available to subclasses,
|
153
|
+
# each subclass has a copy of parent's attribute.
|
154
|
+
#
|
155
|
+
# @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for.
|
156
|
+
# @return <Array[#to_s]> Array of attributes converted into inheritable_readers.
|
157
|
+
#
|
158
|
+
# @api public
|
159
|
+
#
|
160
|
+
# @todo Do we want to block instance_reader via :instance_reader => false
|
161
|
+
# @todo It would be preferable that we do something with a Hash passed in
|
162
|
+
# (error out or do the same as other methods above) instead of silently
|
163
|
+
# moving on). In particular, this makes the return value of this function
|
164
|
+
# less useful.
|
165
|
+
def extlib_inheritable_reader(*ivars, &block)
|
166
|
+
options = ivars.extract_options!
|
167
|
+
|
168
|
+
ivars.each do |ivar|
|
169
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
170
|
+
def self.#{ivar}
|
171
|
+
return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar})
|
172
|
+
ivar = superclass.#{ivar}
|
173
|
+
return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}")
|
174
|
+
@#{ivar} = ivar.duplicable? ? ivar.dup : ivar
|
175
|
+
end
|
176
|
+
RUBY
|
177
|
+
unless options[:instance_reader] == false
|
178
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
179
|
+
def #{ivar}
|
180
|
+
self.class.#{ivar}
|
181
|
+
end
|
182
|
+
RUBY
|
183
|
+
end
|
184
|
+
instance_variable_set(:"@#{ivar}", yield) if block_given?
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Defines class-level inheritable attribute writer. Attributes are available to subclasses,
|
189
|
+
# each subclass has a copy of parent's attribute.
|
190
|
+
#
|
191
|
+
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
|
192
|
+
# define inheritable writer for.
|
193
|
+
# @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
|
194
|
+
# @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers.
|
195
|
+
#
|
196
|
+
# @api public
|
197
|
+
#
|
198
|
+
# @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
|
199
|
+
# class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
|
200
|
+
def extlib_inheritable_writer(*ivars)
|
201
|
+
options = ivars.extract_options!
|
202
|
+
|
203
|
+
ivars.each do |ivar|
|
204
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
205
|
+
def self.#{ivar}=(obj)
|
206
|
+
@#{ivar} = obj
|
207
|
+
end
|
208
|
+
RUBY
|
209
|
+
unless options[:instance_writer] == false
|
210
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
211
|
+
def #{ivar}=(obj) self.class.#{ivar} = obj end
|
212
|
+
RUBY
|
213
|
+
end
|
214
|
+
|
215
|
+
self.send("#{ivar}=", yield) if block_given?
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
|
220
|
+
# each subclass has a copy of parent's attribute.
|
221
|
+
#
|
222
|
+
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
|
223
|
+
# define inheritable accessor for.
|
224
|
+
# @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
|
225
|
+
# @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
|
226
|
+
#
|
227
|
+
# @api public
|
228
|
+
def extlib_inheritable_accessor(*syms, &block)
|
229
|
+
extlib_inheritable_reader(*syms)
|
230
|
+
extlib_inheritable_writer(*syms, &block)
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
module Kernel
|
3
|
+
# Sets $VERBOSE to nil for the duration of the block and back to its original value afterwards.
|
4
|
+
#
|
5
|
+
# silence_warnings do
|
6
|
+
# value = noisy_call # no warning voiced
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# noisy_call # warning voiced
|
10
|
+
def silence_warnings
|
11
|
+
with_warnings(nil) { yield }
|
12
|
+
end
|
13
|
+
|
14
|
+
# Sets $VERBOSE to true for the duration of the block and back to its original value afterwards.
|
15
|
+
def enable_warnings
|
16
|
+
with_warnings(true) { yield }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Sets $VERBOSE for the duration of the block and back to its original value afterwards.
|
20
|
+
def with_warnings(flag)
|
21
|
+
old_verbose, $VERBOSE = $VERBOSE, flag
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
$VERBOSE = old_verbose
|
25
|
+
end
|
26
|
+
|
27
|
+
# For compatibility
|
28
|
+
def silence_stderr #:nodoc:
|
29
|
+
silence_stream(STDERR) { yield }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Silences any stream for the duration of the block.
|
33
|
+
#
|
34
|
+
# silence_stream(STDOUT) do
|
35
|
+
# puts 'This will never be seen'
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# puts 'But this will'
|
39
|
+
def silence_stream(stream)
|
40
|
+
old_stream = stream.dup
|
41
|
+
stream.reopen(Config::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
|
42
|
+
stream.sync = true
|
43
|
+
yield
|
44
|
+
ensure
|
45
|
+
stream.reopen(old_stream)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Blocks and ignores any exception passed as argument if raised within the block.
|
49
|
+
#
|
50
|
+
# suppress(ZeroDivisionError) do
|
51
|
+
# 1/0
|
52
|
+
# puts "This code is NOT reached"
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# puts "This code gets executed and nothing related to ZeroDivisionError was seen"
|
56
|
+
def suppress(*exception_classes)
|
57
|
+
begin yield
|
58
|
+
rescue Exception => e
|
59
|
+
raise unless exception_classes.any? { |cls| e.kind_of?(cls) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|