gorillib 0.0.2
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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/LICENSE.textile +81 -0
- data/README.textile +153 -0
- data/Rakefile +26 -0
- data/VERSION +1 -0
- data/fiddle/hubahuba.rb +62 -0
- data/gorillib.gemspec +142 -0
- data/lib/gorillib/array/compact_blank.rb +17 -0
- data/lib/gorillib/array/extract_options.rb +29 -0
- data/lib/gorillib/base.rb +7 -0
- data/lib/gorillib/datetime/flat.rb +29 -0
- data/lib/gorillib/datetime/parse.rb +21 -0
- data/lib/gorillib/enumerable/sum.rb +38 -0
- data/lib/gorillib/hash/compact.rb +31 -0
- data/lib/gorillib/hash/deep_merge.rb +16 -0
- data/lib/gorillib/hash/keys.rb +42 -0
- data/lib/gorillib/hash/reverse_merge.rb +26 -0
- data/lib/gorillib/hash/slice.rb +53 -0
- data/lib/gorillib/hash/zip.rb +10 -0
- data/lib/gorillib/logger/log.rb +14 -0
- data/lib/gorillib/metaprogramming/aliasing.rb +43 -0
- data/lib/gorillib/metaprogramming/cattr_accessor.rb +79 -0
- data/lib/gorillib/metaprogramming/class_attribute.rb +90 -0
- data/lib/gorillib/metaprogramming/delegation.rb +146 -0
- data/lib/gorillib/metaprogramming/mattr_accessor.rb +61 -0
- data/lib/gorillib/metaprogramming/remove_method.rb +11 -0
- data/lib/gorillib/metaprogramming/singleton_class.rb +8 -0
- data/lib/gorillib/object/blank.rb +89 -0
- data/lib/gorillib/some.rb +12 -0
- data/lib/gorillib/string/constantize.rb +21 -0
- data/lib/gorillib/string/human.rb +52 -0
- data/lib/gorillib/string/inflections.rb +78 -0
- data/lib/gorillib/string/truncate.rb +33 -0
- data/lib/gorillib.rb +1 -0
- data/spec/blank_spec.rb +86 -0
- data/spec/gorillib_spec.rb +7 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/spec_tasks.rake +15 -0
- data/test/abstract_unit.rb +25 -0
- data/test/array/compact_blank_test.rb +33 -0
- data/test/array/extract_options_test.rb +39 -0
- data/test/datetime/flat_test.rb +0 -0
- data/test/datetime/parse_test.rb +0 -0
- data/test/enumerable/sum_test.rb +50 -0
- data/test/hash/compact_test.rb +38 -0
- data/test/hash/deep_merge_test.rb +30 -0
- data/test/hash/keys_test.rb +110 -0
- data/test/hash/reverse_merge_test.rb +20 -0
- data/test/hash/slice_test.rb +47 -0
- data/test/hash/zip_test.rb +0 -0
- data/test/logger/log_test.rb +0 -0
- data/test/metaprogramming/aliasing_test.rb +188 -0
- data/test/metaprogramming/cattr_accessor_test.rb +38 -0
- data/test/metaprogramming/class_attribute_test.rb +73 -0
- data/test/metaprogramming/delegation_test.rb +166 -0
- data/test/metaprogramming/mattr_accessor_test.rb +40 -0
- data/test/metaprogramming/singleton_class_test.rb +9 -0
- data/test/object/blank_test.rb +22 -0
- data/test/string/constantize_test.rb +30 -0
- data/test/string/human_test.rb +65 -0
- data/test/string/inflections_test.rb +57 -0
- data/test/string/inflector_test_cases.rb +50 -0
- data/test/string/truncate_test.rb +37 -0
- metadata +199 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
class Hash
|
2
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
3
|
+
def deep_merge(other_hash)
|
4
|
+
dup.deep_merge!(other_hash)
|
5
|
+
end unless method_defined?(:deep_merge)
|
6
|
+
|
7
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
8
|
+
# Modifies the receiver in place.
|
9
|
+
def deep_merge!(other_hash)
|
10
|
+
other_hash.each_pair do |k,v|
|
11
|
+
tv = self[k]
|
12
|
+
self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
|
13
|
+
end
|
14
|
+
self
|
15
|
+
end unless method_defined?(:deep_merge!)
|
16
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Hash
|
2
|
+
# Return a new hash with all keys converted to strings.
|
3
|
+
def stringify_keys
|
4
|
+
dup.stringify_keys!
|
5
|
+
end unless method_defined?(:stringify_keys)
|
6
|
+
|
7
|
+
# Destructively convert all keys to strings.
|
8
|
+
def stringify_keys!
|
9
|
+
keys.each do |key|
|
10
|
+
self[key.to_s] = delete(key)
|
11
|
+
end
|
12
|
+
self
|
13
|
+
end unless method_defined?(:stringify_keys!)
|
14
|
+
|
15
|
+
# Return a new hash with all keys converted to symbols, as long as
|
16
|
+
# they respond to +to_sym+.
|
17
|
+
def symbolize_keys
|
18
|
+
dup.symbolize_keys!
|
19
|
+
end unless method_defined?(:symbolize_keys)
|
20
|
+
|
21
|
+
# Destructively convert all keys to symbols, as long as they respond
|
22
|
+
# to +to_sym+.
|
23
|
+
def symbolize_keys!
|
24
|
+
keys.each do |key|
|
25
|
+
self[(key.to_sym rescue key) || key] = delete(key)
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end unless method_defined?(:symbolize_keys!)
|
29
|
+
|
30
|
+
# Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
|
31
|
+
# Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
|
32
|
+
# as keys, this will fail.
|
33
|
+
#
|
34
|
+
# ==== Examples
|
35
|
+
# { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years"
|
36
|
+
# { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): name, age"
|
37
|
+
# { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
|
38
|
+
def assert_valid_keys(*valid_keys)
|
39
|
+
unknown_keys = keys - [valid_keys].flatten
|
40
|
+
raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
|
41
|
+
end unless method_defined?(:assert_valid_keys)
|
42
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Hash
|
2
|
+
# Allows for reverse merging two hashes where the keys in the calling hash take precedence over those
|
3
|
+
# in the <tt>other_hash</tt>. This is particularly useful for initializing an option hash with default values:
|
4
|
+
#
|
5
|
+
# def setup(options = {})
|
6
|
+
# options.reverse_merge! :size => 25, :velocity => 10
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# Using <tt>merge</tt>, the above example would look as follows:
|
10
|
+
#
|
11
|
+
# def setup(options = {})
|
12
|
+
# { :size => 25, :velocity => 10 }.merge(options)
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# The default <tt>:size</tt> and <tt>:velocity</tt> are only set if the +options+ hash passed in doesn't already
|
16
|
+
# have the respective key.
|
17
|
+
def reverse_merge(other_hash)
|
18
|
+
other_hash.merge(self)
|
19
|
+
end unless method_defined?(:reverse_merge)
|
20
|
+
|
21
|
+
# Performs the opposite of <tt>merge</tt>, with the keys and values from the first hash taking precedence over the second.
|
22
|
+
# Modifies the receiver in place.
|
23
|
+
def reverse_merge!(other_hash)
|
24
|
+
merge!( other_hash ){|k,o,n| o }
|
25
|
+
end unless method_defined?(:reverse_merge!)
|
26
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Hash
|
2
|
+
# Slice a hash to include only the given keys. This is useful for
|
3
|
+
# limiting an options hash to valid keys before passing to a method:
|
4
|
+
#
|
5
|
+
# def search(criteria = {})
|
6
|
+
# assert_valid_keys(:mass, :velocity, :time)
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# search(options.slice(:mass, :velocity, :time))
|
10
|
+
#
|
11
|
+
# If you have an array of keys you want to limit to, you should splat them:
|
12
|
+
#
|
13
|
+
# valid_keys = [:mass, :velocity, :time]
|
14
|
+
# search(options.slice(*valid_keys))
|
15
|
+
def slice(*keys)
|
16
|
+
keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
|
17
|
+
hash = self.class.new
|
18
|
+
keys.each { |k| hash[k] = self[k] if has_key?(k) }
|
19
|
+
hash
|
20
|
+
end unless method_defined?(:slice)
|
21
|
+
|
22
|
+
# Replaces the hash with only the given keys.
|
23
|
+
# Returns a hash containing the removed key/value pairs
|
24
|
+
# @example
|
25
|
+
# hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
|
26
|
+
# hsh.slice!(:a, :b)
|
27
|
+
# # => {:c => 3, :d =>4}
|
28
|
+
# hsh
|
29
|
+
# # => {:a => 1, :b => 2}
|
30
|
+
def slice!(*keys)
|
31
|
+
keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
|
32
|
+
omit = slice(*self.keys - keys)
|
33
|
+
hash = slice(*keys)
|
34
|
+
replace(hash)
|
35
|
+
omit
|
36
|
+
end unless method_defined?(:slice!)
|
37
|
+
|
38
|
+
# Removes the given keys from the hash
|
39
|
+
# Returns a hash containing the removed key/value pairs
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
|
43
|
+
# hsh.extract!(:a, :b)
|
44
|
+
# # => {:a => 1, :b => 2}
|
45
|
+
# hsh
|
46
|
+
# # => {:c => 3, :d =>4}
|
47
|
+
def extract!(*keys)
|
48
|
+
result = {}
|
49
|
+
keys.each {|key| result[key] = delete(key) }
|
50
|
+
result
|
51
|
+
end unless method_defined?(:extract!)
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Hash
|
2
|
+
#
|
3
|
+
# Create a hash from an array of keys and corresponding values.
|
4
|
+
#
|
5
|
+
def self.zip(keys, values, default=nil, &block)
|
6
|
+
hash = block_given? ? Hash.new(&block) : Hash.new(default)
|
7
|
+
keys.zip(values){|key,val| hash[key]=val }
|
8
|
+
hash
|
9
|
+
end unless respond_to?(:zip)
|
10
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
#
|
4
|
+
# A convenient logger.
|
5
|
+
#
|
6
|
+
# define Log yourself to prevent its creation
|
7
|
+
#
|
8
|
+
::Log = Logger.new(STDERR) unless defined?(::Log)
|
9
|
+
|
10
|
+
def Log.dump *args
|
11
|
+
debug args.map(&:inspect).join("\t")
|
12
|
+
end unless Log.respond_to?(:dump)
|
13
|
+
|
14
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Module
|
2
|
+
# Encapsulates the common pattern of:
|
3
|
+
#
|
4
|
+
# alias_method :foo_without_feature, :foo
|
5
|
+
# alias_method :foo, :foo_with_feature
|
6
|
+
#
|
7
|
+
# With this, you simply do:
|
8
|
+
#
|
9
|
+
# alias_method_chain :foo, :feature
|
10
|
+
#
|
11
|
+
# And both aliases are set up for you.
|
12
|
+
#
|
13
|
+
# Query and bang methods (foo?, foo!) keep the same punctuation:
|
14
|
+
#
|
15
|
+
# alias_method_chain :foo?, :feature
|
16
|
+
#
|
17
|
+
# is equivalent to
|
18
|
+
#
|
19
|
+
# alias_method :foo_without_feature?, :foo?
|
20
|
+
# alias_method :foo?, :foo_with_feature?
|
21
|
+
#
|
22
|
+
# so you can safely chain foo, foo?, and foo! with the same feature.
|
23
|
+
def alias_method_chain(target, feature)
|
24
|
+
# Strip out punctuation on predicates or bang methods since
|
25
|
+
# e.g. target?_without_feature is not a valid method name.
|
26
|
+
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
|
27
|
+
yield(aliased_target, punctuation) if block_given?
|
28
|
+
|
29
|
+
with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
|
30
|
+
|
31
|
+
alias_method without_method, target
|
32
|
+
alias_method target, with_method
|
33
|
+
|
34
|
+
case
|
35
|
+
when public_method_defined?(without_method)
|
36
|
+
public target
|
37
|
+
when protected_method_defined?(without_method)
|
38
|
+
protected target
|
39
|
+
when private_method_defined?(without_method)
|
40
|
+
private target
|
41
|
+
end
|
42
|
+
end unless method_defined?(:alias_method_chain)
|
43
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'gorillib/array/extract_options'
|
2
|
+
|
3
|
+
# Extends the class object with class and instance accessors for class attributes,
|
4
|
+
# just like the native attr* accessors for instance attributes.
|
5
|
+
#
|
6
|
+
# Note that unlike +class_attribute+, if a subclass changes the value then that would
|
7
|
+
# also change the value for parent class. Similarly if parent class changes the value
|
8
|
+
# then that would change the value of subclasses too.
|
9
|
+
#
|
10
|
+
# class Person
|
11
|
+
# cattr_accessor :hair_colors
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Person.hair_colors = [:brown, :black, :blonde, :red]
|
15
|
+
# Person.hair_colors # => [:brown, :black, :blonde, :red]
|
16
|
+
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
|
17
|
+
#
|
18
|
+
# To opt out of the instance writer method, pass :instance_writer => false.
|
19
|
+
# To opt out of the instance reader method, pass :instance_reader => false.
|
20
|
+
#
|
21
|
+
# class Person
|
22
|
+
# cattr_accessor :hair_colors, :instance_writer => false, :instance_reader => false
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# Person.new.hair_colors = [:brown] # => NoMethodError
|
26
|
+
# Person.new.hair_colors # => NoMethodError
|
27
|
+
class Class
|
28
|
+
def cattr_reader(*syms)
|
29
|
+
options = syms.extract_options!
|
30
|
+
syms.each do |sym|
|
31
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
32
|
+
unless defined? @@#{sym}
|
33
|
+
@@#{sym} = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.#{sym}
|
37
|
+
@@#{sym}
|
38
|
+
end
|
39
|
+
EOS
|
40
|
+
|
41
|
+
unless options[:instance_reader] == false
|
42
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
43
|
+
def #{sym}
|
44
|
+
@@#{sym}
|
45
|
+
end
|
46
|
+
EOS
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end unless method_defined?(:cattr_reader)
|
50
|
+
|
51
|
+
def cattr_writer(*syms)
|
52
|
+
options = syms.extract_options!
|
53
|
+
syms.each do |sym|
|
54
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
55
|
+
unless defined? @@#{sym}
|
56
|
+
@@#{sym} = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.#{sym}=(obj)
|
60
|
+
@@#{sym} = obj
|
61
|
+
end
|
62
|
+
EOS
|
63
|
+
|
64
|
+
unless options[:instance_writer] == false
|
65
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
66
|
+
def #{sym}=(obj)
|
67
|
+
@@#{sym} = obj
|
68
|
+
end
|
69
|
+
EOS
|
70
|
+
end
|
71
|
+
self.send("#{sym}=", yield) if block_given?
|
72
|
+
end
|
73
|
+
end unless method_defined?(:cattr_writer)
|
74
|
+
|
75
|
+
def cattr_accessor(*syms, &blk)
|
76
|
+
cattr_reader(*syms)
|
77
|
+
cattr_writer(*syms, &blk)
|
78
|
+
end unless method_defined?(:cattr_accessor)
|
79
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'gorillib/metaprogramming/singleton_class'
|
2
|
+
require 'gorillib/metaprogramming/remove_method'
|
3
|
+
|
4
|
+
class Class
|
5
|
+
# Declare a class-level attribute whose value is inheritable by subclasses.
|
6
|
+
# Subclasses can change their own value and it will not impact parent class.
|
7
|
+
#
|
8
|
+
# class Base
|
9
|
+
# class_attribute :setting
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# class Subclass < Base
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# Base.setting = true
|
16
|
+
# Subclass.setting # => true
|
17
|
+
# Subclass.setting = false
|
18
|
+
# Subclass.setting # => false
|
19
|
+
# Base.setting # => true
|
20
|
+
#
|
21
|
+
# In the above case as long as Subclass does not assign a value to setting
|
22
|
+
# by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
|
23
|
+
# would read value assigned to parent class. Once Subclass assigns a value then
|
24
|
+
# the value assigned by Subclass would be returned.
|
25
|
+
#
|
26
|
+
# This matches normal Ruby method inheritance: think of writing an attribute
|
27
|
+
# on a subclass as overriding the reader method. However, you need to be aware
|
28
|
+
# when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
|
29
|
+
# In such cases, you don't want to do changes in places but use setters:
|
30
|
+
#
|
31
|
+
# Base.setting = []
|
32
|
+
# Base.setting # => []
|
33
|
+
# Subclass.setting # => []
|
34
|
+
#
|
35
|
+
# # Appending in child changes both parent and child because it is the same object:
|
36
|
+
# Subclass.setting << :foo
|
37
|
+
# Base.setting # => [:foo]
|
38
|
+
# Subclass.setting # => [:foo]
|
39
|
+
#
|
40
|
+
# # Use setters to not propagate changes:
|
41
|
+
# Base.setting = []
|
42
|
+
# Subclass.setting += [:foo]
|
43
|
+
# Base.setting # => []
|
44
|
+
# Subclass.setting # => [:foo]
|
45
|
+
#
|
46
|
+
# For convenience, a query method is defined as well:
|
47
|
+
#
|
48
|
+
# Subclass.setting? # => false
|
49
|
+
#
|
50
|
+
# Instances may overwrite the class value in the same way:
|
51
|
+
#
|
52
|
+
# Base.setting = true
|
53
|
+
# object = Base.new
|
54
|
+
# object.setting # => true
|
55
|
+
# object.setting = false
|
56
|
+
# object.setting # => false
|
57
|
+
# Base.setting # => true
|
58
|
+
#
|
59
|
+
# To opt out of the instance writer method, pass :instance_writer => false.
|
60
|
+
#
|
61
|
+
# object.setting = false # => NoMethodError
|
62
|
+
def class_attribute(*attrs)
|
63
|
+
instance_writer = !attrs.last.is_a?(Hash) || attrs.pop[:instance_writer]
|
64
|
+
|
65
|
+
attrs.each do |name|
|
66
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
67
|
+
def self.#{name}() nil end
|
68
|
+
def self.#{name}?() !!#{name} end
|
69
|
+
|
70
|
+
def self.#{name}=(val)
|
71
|
+
singleton_class.class_eval do
|
72
|
+
remove_possible_method(:#{name})
|
73
|
+
define_method(:#{name}) { val }
|
74
|
+
end
|
75
|
+
val
|
76
|
+
end
|
77
|
+
|
78
|
+
def #{name}
|
79
|
+
defined?(@#{name}) ? @#{name} : singleton_class.#{name}
|
80
|
+
end
|
81
|
+
|
82
|
+
def #{name}?
|
83
|
+
!!#{name}
|
84
|
+
end
|
85
|
+
RUBY
|
86
|
+
|
87
|
+
attr_writer name if instance_writer
|
88
|
+
end
|
89
|
+
end unless method_defined?(:class_attribute)
|
90
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require "gorillib/metaprogramming/remove_method"
|
2
|
+
|
3
|
+
class Module
|
4
|
+
# Provides a delegate class method to easily expose contained objects' methods
|
5
|
+
# as your own. Pass one or more methods (specified as symbols or strings)
|
6
|
+
# and the name of the target object via the <tt>:to</tt> option (also a symbol
|
7
|
+
# or string). At least one method and the <tt>:to</tt> option are required.
|
8
|
+
#
|
9
|
+
# Delegation is particularly useful with Active Record associations:
|
10
|
+
#
|
11
|
+
# class Greeter < ActiveRecord::Base
|
12
|
+
# def hello
|
13
|
+
# "hello"
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# def goodbye
|
17
|
+
# "goodbye"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# class Foo < ActiveRecord::Base
|
22
|
+
# belongs_to :greeter
|
23
|
+
# delegate :hello, :to => :greeter
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Foo.new.hello # => "hello"
|
27
|
+
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
|
28
|
+
#
|
29
|
+
# Multiple delegates to the same target are allowed:
|
30
|
+
#
|
31
|
+
# class Foo < ActiveRecord::Base
|
32
|
+
# belongs_to :greeter
|
33
|
+
# delegate :hello, :goodbye, :to => :greeter
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# Foo.new.goodbye # => "goodbye"
|
37
|
+
#
|
38
|
+
# Methods can be delegated to instance variables, class variables, or constants
|
39
|
+
# by providing them as a symbols:
|
40
|
+
#
|
41
|
+
# class Foo
|
42
|
+
# CONSTANT_ARRAY = [0,1,2,3]
|
43
|
+
# @@class_array = [4,5,6,7]
|
44
|
+
#
|
45
|
+
# def initialize
|
46
|
+
# @instance_array = [8,9,10,11]
|
47
|
+
# end
|
48
|
+
# delegate :sum, :to => :CONSTANT_ARRAY
|
49
|
+
# delegate :min, :to => :@@class_array
|
50
|
+
# delegate :max, :to => :@instance_array
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# Foo.new.sum # => 6
|
54
|
+
# Foo.new.min # => 4
|
55
|
+
# Foo.new.max # => 11
|
56
|
+
#
|
57
|
+
# Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
|
58
|
+
# is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
|
59
|
+
# delegated to.
|
60
|
+
#
|
61
|
+
# Person = Struct.new(:name, :address)
|
62
|
+
#
|
63
|
+
# class Invoice < Struct.new(:client)
|
64
|
+
# delegate :name, :address, :to => :client, :prefix => true
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# john_doe = Person.new("John Doe", "Vimmersvej 13")
|
68
|
+
# invoice = Invoice.new(john_doe)
|
69
|
+
# invoice.client_name # => "John Doe"
|
70
|
+
# invoice.client_address # => "Vimmersvej 13"
|
71
|
+
#
|
72
|
+
# It is also possible to supply a custom prefix.
|
73
|
+
#
|
74
|
+
# class Invoice < Struct.new(:client)
|
75
|
+
# delegate :name, :address, :to => :client, :prefix => :customer
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# invoice = Invoice.new(john_doe)
|
79
|
+
# invoice.customer_name # => "John Doe"
|
80
|
+
# invoice.customer_address # => "Vimmersvej 13"
|
81
|
+
#
|
82
|
+
# If the delegate object is +nil+ an exception is raised, and that happens
|
83
|
+
# no matter whether +nil+ responds to the delegated method. You can get a
|
84
|
+
# +nil+ instead with the +:allow_nil+ option.
|
85
|
+
#
|
86
|
+
# class Foo
|
87
|
+
# attr_accessor :bar
|
88
|
+
# def initialize(bar = nil)
|
89
|
+
# @bar = bar
|
90
|
+
# end
|
91
|
+
# delegate :zoo, :to => :bar
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
|
95
|
+
#
|
96
|
+
# class Foo
|
97
|
+
# attr_accessor :bar
|
98
|
+
# def initialize(bar = nil)
|
99
|
+
# @bar = bar
|
100
|
+
# end
|
101
|
+
# delegate :zoo, :to => :bar, :allow_nil => true
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# Foo.new.zoo # returns nil
|
105
|
+
#
|
106
|
+
def delegate(*methods)
|
107
|
+
options = methods.pop
|
108
|
+
unless options.is_a?(Hash) && to = options[:to]
|
109
|
+
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
|
110
|
+
end
|
111
|
+
|
112
|
+
if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/
|
113
|
+
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
|
114
|
+
end
|
115
|
+
|
116
|
+
prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_" || ''
|
117
|
+
|
118
|
+
file, line = caller.first.split(':', 2)
|
119
|
+
line = line.to_i
|
120
|
+
|
121
|
+
methods.each do |method|
|
122
|
+
on_nil =
|
123
|
+
if options[:allow_nil]
|
124
|
+
'return'
|
125
|
+
else
|
126
|
+
%(raise "#{self}##{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
|
127
|
+
end
|
128
|
+
|
129
|
+
module_eval(<<-EOS, file, line - 5)
|
130
|
+
if instance_methods(false).map(&:to_s).include?("#{prefix}#{method}")
|
131
|
+
remove_possible_method("#{prefix}#{method}")
|
132
|
+
end
|
133
|
+
|
134
|
+
def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
|
135
|
+
#{to}.__send__(#{method.inspect}, *args, &block) # client.__send__(:name, *args, &block)
|
136
|
+
rescue NoMethodError # rescue NoMethodError
|
137
|
+
if #{to}.nil? # if client.nil?
|
138
|
+
#{on_nil} # return # depends on :allow_nil
|
139
|
+
else # else
|
140
|
+
raise # raise
|
141
|
+
end # end
|
142
|
+
end # end
|
143
|
+
EOS
|
144
|
+
end
|
145
|
+
end unless method_defined?(:delegate)
|
146
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'gorillib/array/extract_options'
|
2
|
+
|
3
|
+
class Module
|
4
|
+
def mattr_reader(*syms)
|
5
|
+
options = syms.extract_options!
|
6
|
+
syms.each do |sym|
|
7
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
8
|
+
@@#{sym} = nil unless defined? @@#{sym}
|
9
|
+
|
10
|
+
def self.#{sym}
|
11
|
+
@@#{sym}
|
12
|
+
end
|
13
|
+
EOS
|
14
|
+
|
15
|
+
unless options[:instance_reader] == false
|
16
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
17
|
+
def #{sym}
|
18
|
+
@@#{sym}
|
19
|
+
end
|
20
|
+
EOS
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end unless method_defined?(:mattr_reader)
|
24
|
+
|
25
|
+
def mattr_writer(*syms)
|
26
|
+
options = syms.extract_options!
|
27
|
+
syms.each do |sym|
|
28
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
29
|
+
def self.#{sym}=(obj)
|
30
|
+
@@#{sym} = obj
|
31
|
+
end
|
32
|
+
EOS
|
33
|
+
|
34
|
+
unless options[:instance_writer] == false
|
35
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
36
|
+
def #{sym}=(obj)
|
37
|
+
@@#{sym} = obj
|
38
|
+
end
|
39
|
+
EOS
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end unless method_defined?(:mattr_writer)
|
43
|
+
|
44
|
+
# Extends the module object with module and instance accessors for class attributes,
|
45
|
+
# just like the native attr* accessors for instance attributes.
|
46
|
+
#
|
47
|
+
# module AppConfiguration
|
48
|
+
# mattr_accessor :google_api_key
|
49
|
+
# self.google_api_key = "123456789"
|
50
|
+
#
|
51
|
+
# mattr_accessor :paypal_url
|
52
|
+
# self.paypal_url = "www.sandbox.paypal.com"
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# AppConfiguration.google_api_key = "overriding the api key!"
|
56
|
+
def mattr_accessor(*syms)
|
57
|
+
mattr_reader(*syms)
|
58
|
+
mattr_writer(*syms)
|
59
|
+
end unless method_defined?(:mattr_accessor)
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Module
|
2
|
+
def remove_possible_method(method)
|
3
|
+
remove_method(method)
|
4
|
+
rescue NameError
|
5
|
+
end unless method_defined?(:remove_possible_method)
|
6
|
+
|
7
|
+
def redefine_method(method, &block)
|
8
|
+
remove_possible_method(method)
|
9
|
+
define_method(method, &block)
|
10
|
+
end unless method_defined?(:redefine_method)
|
11
|
+
end
|