gorillib 0.1.11 → 0.4.0pre
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/.gitignore +1 -0
- data/.rspec +1 -2
- data/.yardopts +9 -0
- data/{CHANGELOG.textile → CHANGELOG.md} +35 -9
- data/Gemfile +21 -14
- data/Guardfile +19 -0
- data/{LICENSE.textile → LICENSE.md} +43 -29
- data/README.md +47 -52
- data/Rakefile +31 -30
- data/TODO.md +32 -0
- data/VERSION +1 -1
- data/examples/builder/ironfan.rb +133 -0
- data/examples/model/simple.rb +17 -0
- data/gorillib.gemspec +106 -86
- data/lib/alt/kernel/call_stack.rb +56 -0
- data/lib/gorillib/array/wrap.rb +53 -0
- data/lib/gorillib/base.rb +3 -3
- data/lib/gorillib/builder/field.rb +5 -0
- data/lib/gorillib/builder.rb +260 -0
- data/lib/gorillib/collection/has_collection.rb +31 -0
- data/lib/gorillib/collection.rb +129 -0
- data/lib/gorillib/configurable.rb +28 -0
- data/lib/gorillib/datetime/{flat.rb → to_flat.rb} +0 -0
- data/lib/gorillib/exception/confidence.rb +17 -0
- data/lib/gorillib/exception/raisers.rb +78 -0
- data/lib/gorillib/hash/mash.rb +202 -0
- data/lib/gorillib/hashlike/slice.rb +53 -19
- data/lib/gorillib/hashlike.rb +5 -3
- data/lib/gorillib/io/system_helpers.rb +30 -0
- data/lib/gorillib/logger/log.rb +18 -0
- data/lib/gorillib/metaprogramming/concern.rb +124 -0
- data/lib/gorillib/model/active_model_conversion.rb +68 -0
- data/lib/gorillib/model/active_model_naming.rb +87 -0
- data/lib/gorillib/model/active_model_shim.rb +33 -0
- data/lib/gorillib/model/base.rb +341 -0
- data/lib/gorillib/model/defaults.rb +71 -0
- data/lib/gorillib/model/errors.rb +14 -0
- data/lib/gorillib/model/factories.rb +372 -0
- data/lib/gorillib/model/field.rb +146 -0
- data/lib/gorillib/model/named_schema.rb +53 -0
- data/lib/gorillib/{struct/hashlike_iteration.rb → model/overlay.rb} +0 -0
- data/lib/gorillib/model/record_schema.rb +9 -0
- data/lib/gorillib/model/serialization.rb +23 -0
- data/lib/gorillib/model/validate.rb +22 -0
- data/lib/gorillib/model.rb +23 -0
- data/lib/gorillib/pathname.rb +78 -0
- data/lib/gorillib/{serialization.rb → serialization/to_wire.rb} +0 -0
- data/lib/gorillib/some.rb +11 -9
- data/lib/gorillib/string/constantize.rb +21 -14
- data/lib/gorillib/string/inflections.rb +6 -76
- data/lib/gorillib/string/inflector.rb +192 -0
- data/lib/gorillib/string/simple_inflector.rb +267 -0
- data/lib/gorillib/type/extended.rb +52 -0
- data/lib/gorillib/utils/capture_output.rb +28 -0
- data/lib/gorillib/utils/console.rb +131 -0
- data/lib/gorillib/utils/nuke_constants.rb +9 -0
- data/lib/gorillib/utils/stub_module.rb +33 -0
- data/spec/examples/builder/ironfan_spec.rb +37 -0
- data/spec/extlib/hash_spec.rb +64 -0
- data/spec/extlib/mash_spec.rb +312 -0
- data/spec/{array → gorillib/array}/compact_blank_spec.rb +2 -2
- data/spec/{array → gorillib/array}/extract_options_spec.rb +2 -2
- data/spec/gorillib/builder_spec.rb +187 -0
- data/spec/gorillib/collection_spec.rb +20 -0
- data/spec/gorillib/configurable_spec.rb +62 -0
- data/spec/{datetime → gorillib/datetime}/parse_spec.rb +3 -3
- data/spec/{datetime/flat_spec.rb → gorillib/datetime/to_flat_spec.rb} +4 -4
- data/spec/{enumerable → gorillib/enumerable}/sum_spec.rb +5 -5
- data/spec/gorillib/exception/raisers_spec.rb +60 -0
- data/spec/{hash → gorillib/hash}/compact_spec.rb +2 -2
- data/spec/{hash → gorillib/hash}/deep_compact_spec.rb +3 -3
- data/spec/{hash → gorillib/hash}/deep_merge_spec.rb +2 -2
- data/spec/{hash → gorillib/hash}/keys_spec.rb +2 -2
- data/spec/{hash → gorillib/hash}/reverse_merge_spec.rb +2 -2
- data/spec/{hash → gorillib/hash}/slice_spec.rb +2 -2
- data/spec/{hash → gorillib/hash}/zip_spec.rb +2 -2
- data/spec/{hashlike → gorillib/hashlike}/behave_same_as_hash_spec.rb +6 -3
- data/spec/{hashlike → gorillib/hashlike}/deep_hash_spec.rb +2 -2
- data/spec/{hashlike → gorillib/hashlike}/hashlike_behavior_spec.rb +32 -30
- data/spec/{hashlike → gorillib/hashlike}/hashlike_via_accessors_spec.rb +3 -3
- data/spec/{hashlike_spec.rb → gorillib/hashlike_spec.rb} +3 -3
- data/spec/{logger → gorillib/logger}/log_spec.rb +2 -2
- data/spec/{metaprogramming → gorillib/metaprogramming}/aliasing_spec.rb +3 -3
- data/spec/{metaprogramming → gorillib/metaprogramming}/class_attribute_spec.rb +3 -3
- data/spec/{metaprogramming → gorillib/metaprogramming}/delegation_spec.rb +3 -3
- data/spec/{metaprogramming → gorillib/metaprogramming}/singleton_class_spec.rb +3 -3
- data/spec/gorillib/model/record/defaults_spec.rb +108 -0
- data/spec/gorillib/model/record/factories_spec.rb +321 -0
- data/spec/gorillib/model/record/overlay_spec.rb +46 -0
- data/spec/gorillib/model/serialization_spec.rb +48 -0
- data/spec/gorillib/model_spec.rb +281 -0
- data/spec/{numeric → gorillib/numeric}/clamp_spec.rb +2 -2
- data/spec/{object → gorillib/object}/blank_spec.rb +2 -2
- data/spec/{object → gorillib/object}/try_dup_spec.rb +2 -2
- data/spec/{object → gorillib/object}/try_spec.rb +3 -2
- data/spec/gorillib/pathname_spec.rb +114 -0
- data/spec/{string → gorillib/string}/constantize_spec.rb +2 -2
- data/spec/{string → gorillib/string}/human_spec.rb +2 -2
- data/spec/{string → gorillib/string}/inflections_spec.rb +4 -3
- data/spec/{string → gorillib/string}/inflector_test_cases.rb +0 -0
- data/spec/{string → gorillib/string}/truncate_spec.rb +4 -10
- data/spec/gorillib/type/extended_spec.rb +120 -0
- data/spec/gorillib/utils/capture_output_spec.rb +71 -0
- data/spec/spec_helper.rb +8 -11
- data/spec/support/gorillib_test_helpers.rb +66 -0
- data/spec/support/hashlike_fuzzing_helper.rb +31 -33
- data/spec/support/hashlike_helper.rb +3 -3
- data/spec/support/model_test_helpers.rb +81 -0
- data/spec/support/shared_examples/included_module.rb +20 -0
- metadata +177 -158
- data/lib/gorillib/array/average.rb +0 -13
- data/lib/gorillib/array/sorted_median.rb +0 -11
- data/lib/gorillib/array/sorted_percentile.rb +0 -11
- data/lib/gorillib/array/sorted_sample.rb +0 -12
- data/lib/gorillib/dsl_object.rb +0 -64
- data/lib/gorillib/hash/indifferent_access.rb +0 -207
- data/lib/gorillib/hash/tree_merge.rb +0 -4
- data/lib/gorillib/hashlike/tree_merge.rb +0 -49
- data/lib/gorillib/metaprogramming/cattr_accessor.rb +0 -79
- data/lib/gorillib/metaprogramming/mattr_accessor.rb +0 -61
- data/lib/gorillib/receiver/active_model_shim.rb +0 -32
- data/lib/gorillib/receiver/acts_as_hash.rb +0 -195
- data/lib/gorillib/receiver/acts_as_loadable.rb +0 -42
- data/lib/gorillib/receiver/locale/en.yml +0 -27
- data/lib/gorillib/receiver/tree_diff.rb +0 -74
- data/lib/gorillib/receiver/validations.rb +0 -30
- data/lib/gorillib/receiver.rb +0 -402
- data/lib/gorillib/receiver_model.rb +0 -21
- data/lib/gorillib/struct/acts_as_hash.rb +0 -108
- data/notes/fancy_hashes_and_receivers.textile +0 -120
- data/notes/hash_rdocs.textile +0 -97
- data/spec/array/average_spec.rb +0 -24
- data/spec/array/sorted_median_spec.rb +0 -18
- data/spec/array/sorted_percentile_spec.rb +0 -24
- data/spec/array/sorted_sample_spec.rb +0 -28
- data/spec/dsl_object_spec.rb +0 -99
- data/spec/hash/indifferent_access_spec.rb +0 -391
- data/spec/metaprogramming/cattr_accessor_spec.rb +0 -43
- data/spec/metaprogramming/mattr_accessor_spec.rb +0 -45
- data/spec/receiver/acts_as_hash_spec.rb +0 -295
- data/spec/receiver_spec.rb +0 -551
- data/spec/struct/acts_as_hash_fuzz_spec.rb +0 -71
- data/spec/struct/acts_as_hash_spec.rb +0 -422
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
class Hash
|
|
2
|
+
|
|
3
|
+
# Convert to Mash. This class has semantics of ActiveSupport's
|
|
4
|
+
# HashWithIndifferentAccess and we only have it so that people can write
|
|
5
|
+
# params[:key] instead of params['key'].
|
|
6
|
+
#
|
|
7
|
+
# @return [Mash] This hash as a Mash for string or symbol key access.
|
|
8
|
+
def to_mash
|
|
9
|
+
hash = Mash.new(self)
|
|
10
|
+
hash.default = default
|
|
11
|
+
hash
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Create a hash with *only* key/value pairs in receiver and +allowed+
|
|
16
|
+
#
|
|
17
|
+
# { :one => 1, :two => 2, :three => 3 }.only(:one) #=> { :one => 1 }
|
|
18
|
+
#
|
|
19
|
+
# @param allowed [Array[String, Symbol]] The hash keys to include.
|
|
20
|
+
#
|
|
21
|
+
# @return [Hash] A new hash with only the selected keys.
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
24
|
+
def only(*allowed)
|
|
25
|
+
hash = {}
|
|
26
|
+
allowed.each {|k| hash[k] = self[k] if self.has_key?(k) }
|
|
27
|
+
hash
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Create a hash with all key/value pairs in receiver *except* +rejected+
|
|
32
|
+
#
|
|
33
|
+
# { :one => 1, :two => 2, :three => 3 }.except(:one)
|
|
34
|
+
# #=> { :two => 2, :three => 3 }
|
|
35
|
+
#
|
|
36
|
+
# @param rejected [Array[String, Symbol]] The hash keys to exclude.
|
|
37
|
+
#
|
|
38
|
+
# @return [Hash] A new hash without the selected keys.
|
|
39
|
+
#
|
|
40
|
+
# @api public
|
|
41
|
+
def except(*rejected)
|
|
42
|
+
hash = self.dup
|
|
43
|
+
rejected.each {|k| hash.delete(k) }
|
|
44
|
+
hash
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# This class has dubious semantics and we only have it so that people can write
|
|
49
|
+
# params[:key] instead of params['key'].
|
|
50
|
+
class Mash < Hash
|
|
51
|
+
|
|
52
|
+
# @param constructor [Object]
|
|
53
|
+
# The default value for the mash. Defaults to an empty hash.
|
|
54
|
+
#
|
|
55
|
+
# @overload [Alternatives]
|
|
56
|
+
# If constructor is a Hash, a new mash will be created based on the keys of
|
|
57
|
+
# the hash and no default value will be set.
|
|
58
|
+
def initialize(constructor = {})
|
|
59
|
+
if constructor.is_a?(Hash)
|
|
60
|
+
super()
|
|
61
|
+
update(constructor)
|
|
62
|
+
else
|
|
63
|
+
super(constructor)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @param key<Object> The default value for the mash. Defaults to nil.
|
|
68
|
+
#
|
|
69
|
+
# @overload [Alternatives]
|
|
70
|
+
# If key is a Symbol and it is a key in the mash, then the default value will
|
|
71
|
+
# be set to the value matching the key.
|
|
72
|
+
def default(key = nil)
|
|
73
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
|
74
|
+
self[key]
|
|
75
|
+
else
|
|
76
|
+
super
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
|
81
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
|
82
|
+
|
|
83
|
+
# @param key<Object> The key to set.
|
|
84
|
+
# @param value<Object>
|
|
85
|
+
# The value to set the key to.
|
|
86
|
+
#
|
|
87
|
+
# @see Mash#convert_key
|
|
88
|
+
# @see Mash#convert_value
|
|
89
|
+
def []=(key, value)
|
|
90
|
+
regular_writer(convert_key(key), convert_value(value))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @param other_hash<Hash>
|
|
94
|
+
# A hash to update values in the mash with. The keys and the values will be
|
|
95
|
+
# converted to Mash format.
|
|
96
|
+
#
|
|
97
|
+
# @return [Mash] The updated mash.
|
|
98
|
+
def update(other_hash)
|
|
99
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
|
100
|
+
self
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
alias_method :merge!, :update
|
|
104
|
+
|
|
105
|
+
# @param key<Object> The key to check for. This will be run through convert_key.
|
|
106
|
+
#
|
|
107
|
+
# @return [Boolean] True if the key exists in the mash.
|
|
108
|
+
def key?(key)
|
|
109
|
+
super(convert_key(key))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# def include? def has_key? def member?
|
|
113
|
+
alias_method :include?, :key?
|
|
114
|
+
alias_method :has_key?, :key?
|
|
115
|
+
alias_method :member?, :key?
|
|
116
|
+
|
|
117
|
+
# @param <Object> key The key to fetch. This will be run through convert_key.
|
|
118
|
+
# @param <Array> extras Default value.
|
|
119
|
+
#
|
|
120
|
+
# @return [Object] The value at key or the default value.
|
|
121
|
+
def fetch(key, *extras)
|
|
122
|
+
super(convert_key(key), *extras)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# @param indices [Array]
|
|
126
|
+
# The keys to retrieve values for. These will be run through +convert_key+.
|
|
127
|
+
#
|
|
128
|
+
# @return [Array] The values at each of the provided keys
|
|
129
|
+
def values_at(*indices)
|
|
130
|
+
indices.collect {|key| self[convert_key(key)]}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# @param hash<Hash> The hash to merge with the mash.
|
|
134
|
+
#
|
|
135
|
+
# @return [Mash] A new mash with the hash values merged in.
|
|
136
|
+
def merge(hash)
|
|
137
|
+
self.dup.update(hash)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @param key<Object>
|
|
141
|
+
# The key to delete from the mash.\
|
|
142
|
+
def delete(key)
|
|
143
|
+
super(convert_key(key))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @param [Array[(String, Symbol)]] rejected The mash keys to exclude.
|
|
147
|
+
#
|
|
148
|
+
# @return [Mash] A new mash without the selected keys.
|
|
149
|
+
#
|
|
150
|
+
# @example
|
|
151
|
+
# { :one => 1, :two => 2, :three => 3 }.except(:one)
|
|
152
|
+
# #=> { "two" => 2, "three" => 3 }
|
|
153
|
+
def except(*rejected)
|
|
154
|
+
super(*rejected.map {|k| convert_key(k)})
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Used to provide the same interface as Hash.
|
|
158
|
+
#
|
|
159
|
+
# @return [Mash] This mash unchanged.
|
|
160
|
+
def stringify_keys!; self end
|
|
161
|
+
|
|
162
|
+
# @return [Hash] The mash as a Hash with symbolized keys.
|
|
163
|
+
def symbolize_keys
|
|
164
|
+
h = Hash.new(default)
|
|
165
|
+
each { |key, val| h[key.to_sym] = val }
|
|
166
|
+
h
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# @return [Hash] The mash as a Hash with string keys.
|
|
170
|
+
def to_hash
|
|
171
|
+
Hash.new(default).merge(self)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
protected
|
|
175
|
+
# @param key [Object] The key to convert.
|
|
176
|
+
#
|
|
177
|
+
# @return [Object]
|
|
178
|
+
# The converted key. If the key was a symbol, it will be converted to a
|
|
179
|
+
# string.
|
|
180
|
+
#
|
|
181
|
+
# @api private
|
|
182
|
+
def convert_key(key)
|
|
183
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @param value [Object] The value to convert.
|
|
187
|
+
#
|
|
188
|
+
# @return [Object]
|
|
189
|
+
# The converted value. A Hash or an Array of hashes, will be converted to
|
|
190
|
+
# their Mash equivalents.
|
|
191
|
+
#
|
|
192
|
+
# @api private
|
|
193
|
+
def convert_value(value)
|
|
194
|
+
if value.class == Hash
|
|
195
|
+
value.to_mash
|
|
196
|
+
elsif value.is_a?(Array)
|
|
197
|
+
value.collect { |e| convert_value(e) }
|
|
198
|
+
else
|
|
199
|
+
value
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
module Gorillib
|
|
2
2
|
module Hashlike
|
|
3
3
|
module Slice
|
|
4
|
-
# Slice a hash to include only the given allowed_keys.
|
|
5
|
-
# limiting an options hash to valid keys before passing to a method:
|
|
4
|
+
# Slice a hash to include only the given allowed_keys.
|
|
6
5
|
#
|
|
6
|
+
# @return the sliced hash
|
|
7
|
+
#
|
|
8
|
+
# @example limit an options hash to valid keys before passing to a method:
|
|
7
9
|
# def search(criteria = {})
|
|
8
10
|
# assert_valid_keys(:mass, :velocity, :time)
|
|
9
11
|
# end
|
|
10
|
-
#
|
|
11
12
|
# search(options.slice(:mass, :velocity, :time))
|
|
12
13
|
#
|
|
13
14
|
# If you have an array of keys you want to limit to, you should splat them:
|
|
@@ -21,8 +22,10 @@ module Gorillib
|
|
|
21
22
|
hash
|
|
22
23
|
end unless method_defined?(:slice)
|
|
23
24
|
|
|
24
|
-
#
|
|
25
|
-
#
|
|
25
|
+
# Replace the hash with only the given allowed_keys.
|
|
26
|
+
#
|
|
27
|
+
# @return a hash containing the removed key/value pairs
|
|
28
|
+
#
|
|
26
29
|
# @example
|
|
27
30
|
# hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
|
|
28
31
|
# hsh.slice!(:a, :b)
|
|
@@ -37,21 +40,9 @@ module Gorillib
|
|
|
37
40
|
omit
|
|
38
41
|
end unless method_defined?(:slice!)
|
|
39
42
|
|
|
40
|
-
# This also works, and doesn't require #replace method, but is uglier and
|
|
41
|
-
# wasn't written by Railsians. I'm not sure that slice! is interesting if
|
|
42
|
-
# you're a duck-typed Hash but not is_a?(Hash), so we'll just leave it at the
|
|
43
|
-
# active_record implementation.
|
|
44
|
-
#
|
|
45
|
-
# def slice!(*allowed_keys)
|
|
46
|
-
# allowed_keys = allowed_keys.map!{|key| convert_key(key) } if respond_to?(:convert_key)
|
|
47
|
-
# omit_keys = self.keys - allowed_keys
|
|
48
|
-
# omit = slice(*omit_keys)
|
|
49
|
-
# omit_keys.each{|k| delete(k) }
|
|
50
|
-
# omit
|
|
51
|
-
# end
|
|
52
|
-
|
|
53
43
|
# Removes the given allowed_keys from the hash
|
|
54
|
-
#
|
|
44
|
+
#
|
|
45
|
+
# @return a hash containing the removed key/value pairs
|
|
55
46
|
#
|
|
56
47
|
# @example
|
|
57
48
|
# hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
|
|
@@ -62,6 +53,49 @@ module Gorillib
|
|
|
62
53
|
def extract!(*allowed_keys)
|
|
63
54
|
slice!(*self.keys - allowed_keys)
|
|
64
55
|
end unless method_defined?(:extract!)
|
|
56
|
+
|
|
57
|
+
# Return a hash that includes everything but the given keys.
|
|
58
|
+
#
|
|
59
|
+
# @example Exclude a blacklist of parameters
|
|
60
|
+
# @person.update_attributes(params[:person].except(:admin))
|
|
61
|
+
#
|
|
62
|
+
# If the receiver responds to +convert_key+, the method is called on each of the
|
|
63
|
+
# arguments. This allows +except+ to play nice with hashes with indifferent access
|
|
64
|
+
# for instance:
|
|
65
|
+
#
|
|
66
|
+
# @example mash, hash, it does the right thing:
|
|
67
|
+
# {:a => 1}.to_mash.except(:a) # => {}
|
|
68
|
+
# {:a => 1}.to_mash.except('a') # => {}
|
|
69
|
+
#
|
|
70
|
+
def except(*keys)
|
|
71
|
+
dup.except!(*keys)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Replaces the hash without the given keys.
|
|
75
|
+
def except!(*keys)
|
|
76
|
+
keys.each{|key| delete(key) }
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def only(*allowed)
|
|
81
|
+
dup.only!(*allowed)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Create a hash with *only* key/value pairs in receiver and +allowed+
|
|
85
|
+
#
|
|
86
|
+
# @example Limit a set of parameters to everything but a few known toggles:
|
|
87
|
+
# { :one => 1, :two => 2, :three => 3 }.only(:one) #=> { :one => 1 }
|
|
88
|
+
#
|
|
89
|
+
# @param [Array[String, Symbol]] allowed The hash keys to include.
|
|
90
|
+
#
|
|
91
|
+
# @return [Hash] A new hash with only the selected keys.
|
|
92
|
+
#
|
|
93
|
+
# @api public
|
|
94
|
+
def only!(*allowed)
|
|
95
|
+
allowed = allowed.to_set
|
|
96
|
+
select!{|key, val| allowed.include?(key) }
|
|
97
|
+
end
|
|
98
|
+
|
|
65
99
|
end
|
|
66
100
|
end
|
|
67
101
|
end
|
data/lib/gorillib/hashlike.rb
CHANGED
|
@@ -199,7 +199,7 @@ module Gorillib
|
|
|
199
199
|
# hsh.values_at(:c, :a, :c, :z, :a)
|
|
200
200
|
# # => [300, 100, 300, nil, 100]
|
|
201
201
|
#
|
|
202
|
-
# @param
|
|
202
|
+
# @param allowed_keys [Object] the keys to retrieve.
|
|
203
203
|
# @return [Array] the values, in order according to allowed_keys.
|
|
204
204
|
#
|
|
205
205
|
def values_at(*allowed_keys)
|
|
@@ -304,7 +304,7 @@ module Gorillib
|
|
|
304
304
|
# hsh.values_of(:c, :a, :c, :z, :a)
|
|
305
305
|
# # => [300, 100, 300, nil, 100]
|
|
306
306
|
#
|
|
307
|
-
# @param
|
|
307
|
+
# @param allowed_keys [Object] the keys to retrieve.
|
|
308
308
|
# @return [Array] the values, in order according to allowed_keys.
|
|
309
309
|
#
|
|
310
310
|
def values_of(*allowed_keys)
|
|
@@ -794,7 +794,9 @@ module Gorillib
|
|
|
794
794
|
# hsh.flatten(nil)
|
|
795
795
|
# # => [1, 2, 3, 4, 1, 2, 3, 4, 5, 6]
|
|
796
796
|
#
|
|
797
|
-
# @
|
|
797
|
+
# @overload flatten
|
|
798
|
+
# @overload flatten(level)
|
|
799
|
+
# @param level [Integer] the level of recursion to flatten, 0 by default.
|
|
798
800
|
# @return [Array] the flattened key-value array.
|
|
799
801
|
#
|
|
800
802
|
def flatten(*args)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Gorillib::CheckedPopen
|
|
2
|
+
module_function
|
|
3
|
+
|
|
4
|
+
def checked_popen(command, mode, fail_action, io_class=IO)
|
|
5
|
+
check_child_exit_status do
|
|
6
|
+
io_class.popen(command, mode) do |process|
|
|
7
|
+
yield(process)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
rescue Errno::EPIPE
|
|
11
|
+
fail_action.call
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @private
|
|
15
|
+
NO_EXIT_STATUS = OpenStruct.new(:exitstatus => 0)
|
|
16
|
+
|
|
17
|
+
def check_child_exit_status
|
|
18
|
+
result = yield
|
|
19
|
+
status = $? || NO_EXIT_STATUS
|
|
20
|
+
unless [0, 172].include?(status.exitstatus)
|
|
21
|
+
raise ArgumentError, "Command exited with status '#{status.exitstatus}'"
|
|
22
|
+
end
|
|
23
|
+
result
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
::IO.class_eval do
|
|
29
|
+
include Gorillib::CheckedPopen
|
|
30
|
+
end
|
data/lib/gorillib/logger/log.rb
CHANGED
|
@@ -7,6 +7,24 @@ require 'logger'
|
|
|
7
7
|
#
|
|
8
8
|
::Log = Logger.new($stderr) unless defined?(::Log)
|
|
9
9
|
|
|
10
|
+
# unless defined?(Log)
|
|
11
|
+
# require 'log4r'
|
|
12
|
+
# Log = Log4r::Logger.new('wukong')
|
|
13
|
+
# Log.outputters = Log4r::Outputter.stderr
|
|
14
|
+
# # require 'logger'
|
|
15
|
+
# # Log = Logger.new(STDERR)
|
|
16
|
+
# end
|
|
17
|
+
|
|
18
|
+
# require 'log_buddy'; LogBuddy.init :log_to_stdout => false, :logger => Log
|
|
19
|
+
# LogBuddy::Utils.module_eval do
|
|
20
|
+
# def arg_and_blk_debug(arg, blk)
|
|
21
|
+
# result = eval(arg, blk.binding)
|
|
22
|
+
# result_str = obj_to_string(result, :quote_strings => true)
|
|
23
|
+
# LogBuddy.debug(%[#{arg} = #{result_str}])
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
|
|
27
|
+
|
|
10
28
|
def Log.dump *args
|
|
11
29
|
self.debug([
|
|
12
30
|
args.map(&:inspect),
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
module Gorillib
|
|
2
|
+
# A typical module looks like this:
|
|
3
|
+
#
|
|
4
|
+
# module M
|
|
5
|
+
# def self.included(base)
|
|
6
|
+
# base.extend ClassMethods
|
|
7
|
+
# scope :disabled, where(:disabled => true)
|
|
8
|
+
# end
|
|
9
|
+
#
|
|
10
|
+
# module ClassMethods
|
|
11
|
+
# ...
|
|
12
|
+
# end
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# By using <tt>Gorillib::Concern</tt> the above module could instead be written as:
|
|
16
|
+
#
|
|
17
|
+
# require 'active_support/concern'
|
|
18
|
+
#
|
|
19
|
+
# module M
|
|
20
|
+
# extend Gorillib::Concern
|
|
21
|
+
#
|
|
22
|
+
# included do
|
|
23
|
+
# scope :disabled, where(:disabled => true)
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# module ClassMethods
|
|
27
|
+
# ...
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# Moreover, it gracefully handles module dependencies. Given a +Foo+ module and a +Bar+
|
|
32
|
+
# module which depends on the former, we would typically write the following:
|
|
33
|
+
#
|
|
34
|
+
# module Foo
|
|
35
|
+
# def self.included(base)
|
|
36
|
+
# base.class_eval do
|
|
37
|
+
# def self.method_injected_by_foo
|
|
38
|
+
# ...
|
|
39
|
+
# end
|
|
40
|
+
# end
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# module Bar
|
|
45
|
+
# def self.included(base)
|
|
46
|
+
# base.method_injected_by_foo
|
|
47
|
+
# end
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# class Host
|
|
51
|
+
# include Foo # We need to include this dependency for Bar
|
|
52
|
+
# include Bar # Bar is the module that Host really needs
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
# But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We could try to hide
|
|
56
|
+
# these from +Host+ directly including +Foo+ in +Bar+:
|
|
57
|
+
#
|
|
58
|
+
# module Bar
|
|
59
|
+
# include Foo
|
|
60
|
+
# def self.included(base)
|
|
61
|
+
# base.method_injected_by_foo
|
|
62
|
+
# end
|
|
63
|
+
# end
|
|
64
|
+
#
|
|
65
|
+
# class Host
|
|
66
|
+
# include Bar
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
# Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt> is the +Bar+ module,
|
|
70
|
+
# not the +Host+ class. With <tt>Gorillib::Concern</tt>, module dependencies are properly resolved:
|
|
71
|
+
#
|
|
72
|
+
# require 'active_support/concern'
|
|
73
|
+
#
|
|
74
|
+
# module Foo
|
|
75
|
+
# extend Gorillib::Concern
|
|
76
|
+
# included do
|
|
77
|
+
# class_eval do
|
|
78
|
+
# def self.method_injected_by_foo
|
|
79
|
+
# ...
|
|
80
|
+
# end
|
|
81
|
+
# end
|
|
82
|
+
# end
|
|
83
|
+
# end
|
|
84
|
+
#
|
|
85
|
+
# module Bar
|
|
86
|
+
# extend Gorillib::Concern
|
|
87
|
+
# include Foo
|
|
88
|
+
#
|
|
89
|
+
# included do
|
|
90
|
+
# self.method_injected_by_foo
|
|
91
|
+
# end
|
|
92
|
+
# end
|
|
93
|
+
#
|
|
94
|
+
# class Host
|
|
95
|
+
# include Bar # works, Bar takes care now of its dependencies
|
|
96
|
+
# end
|
|
97
|
+
#
|
|
98
|
+
module Concern
|
|
99
|
+
def self.extended(base)
|
|
100
|
+
base.instance_variable_set("@_dependencies", [])
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def append_features(base)
|
|
104
|
+
if base.instance_variable_defined?("@_dependencies")
|
|
105
|
+
base.instance_variable_get("@_dependencies") << self
|
|
106
|
+
return false
|
|
107
|
+
else
|
|
108
|
+
return false if base < self
|
|
109
|
+
@_dependencies.each { |dep| base.send(:include, dep) }
|
|
110
|
+
super
|
|
111
|
+
base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
|
|
112
|
+
base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def included(base = nil, &block)
|
|
117
|
+
if base.nil?
|
|
118
|
+
@_included_block = block
|
|
119
|
+
else
|
|
120
|
+
super
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Gorillib::Model
|
|
2
|
+
# == Active Model Conversions
|
|
3
|
+
#
|
|
4
|
+
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
|
|
5
|
+
#
|
|
6
|
+
# Let's take for example this non-persisted object.
|
|
7
|
+
#
|
|
8
|
+
# class ContactMessage
|
|
9
|
+
# include ActiveModel::Conversion
|
|
10
|
+
#
|
|
11
|
+
# # ContactMessage are never persisted in the DB
|
|
12
|
+
# def persisted?
|
|
13
|
+
# false
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# cm = ContactMessage.new
|
|
18
|
+
# cm.to_model == self # => true
|
|
19
|
+
# cm.to_key # => nil
|
|
20
|
+
# cm.to_param # => nil
|
|
21
|
+
# cm.to_path # => "contact_messages/contact_message"
|
|
22
|
+
#
|
|
23
|
+
module Conversion
|
|
24
|
+
extend Gorillib::Concern
|
|
25
|
+
|
|
26
|
+
# If your object is already designed to implement all of the Active Model
|
|
27
|
+
# you can use the default <tt>:to_model</tt> implementation, which simply
|
|
28
|
+
# returns self.
|
|
29
|
+
#
|
|
30
|
+
# If your model does not act like an Active Model object, then you should
|
|
31
|
+
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
|
|
32
|
+
# your object with Active Model compliant methods.
|
|
33
|
+
def to_model
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns an Enumerable of all key attributes if any is set, regardless
|
|
38
|
+
# if the object is persisted or not.
|
|
39
|
+
#
|
|
40
|
+
# Note the default implementation uses persisted? just because all objects
|
|
41
|
+
# in Ruby 1.8.x responds to <tt>:id</tt>.
|
|
42
|
+
def to_key
|
|
43
|
+
persisted? ? [id] : nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns a string representing the object's key suitable for use in URLs,
|
|
47
|
+
# or nil if <tt>persisted?</tt> is false.
|
|
48
|
+
def to_param
|
|
49
|
+
persisted? ? to_key.join('-') : nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns a string identifying the path associated with the object.
|
|
53
|
+
# ActionPack uses this to find a suitable partial to represent the object.
|
|
54
|
+
def to_partial_path
|
|
55
|
+
self.class._to_partial_path
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
module ClassMethods #:nodoc:
|
|
59
|
+
# Provide a class level cache for the to_path. This is an
|
|
60
|
+
# internal method and should not be accessed directly.
|
|
61
|
+
def _to_partial_path #:nodoc:
|
|
62
|
+
@_to_partial_path ||= begin
|
|
63
|
+
"#{model_name.collection}/#{model_name.element}".freeze
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Gorillib::Model
|
|
2
|
+
class Name < String
|
|
3
|
+
attr_accessor :namespace
|
|
4
|
+
attr_reader :singular, :element, :collection, :partial_path, :param_key, :i18n_key
|
|
5
|
+
|
|
6
|
+
alias_method :cache_key, :collection
|
|
7
|
+
|
|
8
|
+
class_attribute :inflector
|
|
9
|
+
self.inflector ||= Gorillib::String::Inflector
|
|
10
|
+
|
|
11
|
+
def initialize(klass, namespace = nil, name = nil)
|
|
12
|
+
name ||= klass.name
|
|
13
|
+
raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if name.blank?
|
|
14
|
+
|
|
15
|
+
super(name)
|
|
16
|
+
|
|
17
|
+
@klass = klass
|
|
18
|
+
@singular = _singularize(self).freeze
|
|
19
|
+
@element = inflector.underscore(inflector.demodulize(self)).freeze
|
|
20
|
+
@human = inflector.humanize(@element).freeze
|
|
21
|
+
@i18n_key = inflector.underscore(self).to_sym
|
|
22
|
+
#
|
|
23
|
+
self.namespace = namespace
|
|
24
|
+
self.plural = inflector.pluralize(@singular)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def plural=(str)
|
|
28
|
+
@plural = str.dup.freeze
|
|
29
|
+
@collection = inflector.underscore(@plural).freeze
|
|
30
|
+
@partial_path = "#{@collection}/#{@element}".freeze
|
|
31
|
+
str
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def namespace=(ns)
|
|
35
|
+
if ns.present?
|
|
36
|
+
@unnamespaced = self.sub(/^#{ns.name}::/, '')
|
|
37
|
+
@param_key = _singularize(@unnamespaced).freeze
|
|
38
|
+
else
|
|
39
|
+
@unnamespaced = nil
|
|
40
|
+
@param_key = @singular.freeze
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def singular_route_key
|
|
45
|
+
inflector.singularize(route_key).freeze
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def route_key
|
|
49
|
+
rk = (namespace ? inflector.pluralize(param_key) : plural.dup)
|
|
50
|
+
rk << "_index" if plural == singular
|
|
51
|
+
rk.freeze
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def _singularize(string, replacement='_')
|
|
57
|
+
inflector.underscore(string).tr('/', replacement)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# == Active Model Naming
|
|
62
|
+
#
|
|
63
|
+
# Creates a +model_name+ method on your object.
|
|
64
|
+
#
|
|
65
|
+
# To implement, just extend ActiveModel::Naming in your object:
|
|
66
|
+
#
|
|
67
|
+
# class BookCover
|
|
68
|
+
# extend ActiveModel::Naming
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# BookCover.model_name # => "BookCover"
|
|
72
|
+
# BookCover.model_name.human # => "Book cover"
|
|
73
|
+
#
|
|
74
|
+
# BookCover.model_name.i18n_key # => :book_cover
|
|
75
|
+
# BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
|
|
76
|
+
#
|
|
77
|
+
# Providing the functionality that ActiveModel::Naming provides in your object
|
|
78
|
+
# is required to pass the Active Model Lint test. So either extending the provided
|
|
79
|
+
# method below, or rolling your own is required.
|
|
80
|
+
module Naming
|
|
81
|
+
# Returns a Name object for module, which can be used to retrieve all kinds
|
|
82
|
+
# of naming-related information.
|
|
83
|
+
def model_name
|
|
84
|
+
@_model_name ||= Gorillib::Model::Name.new(self, namespace)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require "active_model"
|
|
2
|
+
|
|
3
|
+
module Gorillib
|
|
4
|
+
module Model
|
|
5
|
+
|
|
6
|
+
# Provides the minimum functionality to pass the ActiveModel lint tests
|
|
7
|
+
#
|
|
8
|
+
# @example Usage
|
|
9
|
+
# class Person
|
|
10
|
+
# include Gorillib::Model::ActiveModelShim
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
module ActiveModelShim
|
|
14
|
+
extend Gorillib::Concern
|
|
15
|
+
extend ActiveModel::Naming
|
|
16
|
+
include Gorillib::Model::Conversion
|
|
17
|
+
include ActiveModel::Validations
|
|
18
|
+
|
|
19
|
+
# @return [false]
|
|
20
|
+
def persisted?
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def attribute_method?(attr_name)
|
|
25
|
+
self.class.has_field?(attr_name)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module ClassMethods
|
|
29
|
+
end # ActiveModelShim::ClassMethods
|
|
30
|
+
end # ActiveModelShim
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|