gorillib 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|