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,260 @@
|
|
|
1
|
+
require 'gorillib/string/simple_inflector'
|
|
2
|
+
require 'gorillib/model'
|
|
3
|
+
|
|
4
|
+
module Gorillib
|
|
5
|
+
module Builder
|
|
6
|
+
extend Gorillib::Concern
|
|
7
|
+
include Gorillib::Model
|
|
8
|
+
|
|
9
|
+
def initialize(attrs={}, &block)
|
|
10
|
+
receive!(attrs, &block)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def receive!(*args, &block)
|
|
14
|
+
super(*args)
|
|
15
|
+
if block_given?
|
|
16
|
+
(block.arity == 1) ? block.call(self) : self.instance_eval(&block)
|
|
17
|
+
end
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def getset(field, *args, &block)
|
|
22
|
+
ArgumentError.check_arity!(args, 0..1)
|
|
23
|
+
if args.empty?
|
|
24
|
+
read_attribute(field.name)
|
|
25
|
+
else
|
|
26
|
+
write_attribute(field.name, args.first)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def getset_member(field, *args, &block)
|
|
31
|
+
ArgumentError.check_arity!(args, 0..1)
|
|
32
|
+
attrs = args.first
|
|
33
|
+
if attrs.is_a?(field.type)
|
|
34
|
+
# actual object: assign it into field
|
|
35
|
+
val = attrs
|
|
36
|
+
write_attribute(field.name, val)
|
|
37
|
+
else
|
|
38
|
+
val = read_attribute(field.name)
|
|
39
|
+
if val.present?
|
|
40
|
+
# existing item: update it with args and block
|
|
41
|
+
val.receive!(*args, &block) if args.present?
|
|
42
|
+
elsif attrs.blank?
|
|
43
|
+
# missing item (read): return nil
|
|
44
|
+
return nil
|
|
45
|
+
else
|
|
46
|
+
# missing item (write): construct item and add to collection
|
|
47
|
+
options = args.extract_options!.merge(:owner => self)
|
|
48
|
+
val = field.type.receive(*args, options, &block)
|
|
49
|
+
write_attribute(field.name, val)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
val
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def getset_collection_item(field, item_key, attrs={}, &block)
|
|
56
|
+
plural_name = field.plural_name
|
|
57
|
+
if attrs.is_a?(field.item_type)
|
|
58
|
+
# actual object: assign it into collection
|
|
59
|
+
val = attrs
|
|
60
|
+
set_collection_item(plural_name, item_key, val)
|
|
61
|
+
elsif has_collection_item?(plural_name, item_key)
|
|
62
|
+
# existing item: retrieve it, updating as directed
|
|
63
|
+
val = get_collection_item(plural_name, item_key)
|
|
64
|
+
val.receive!(attrs, &block)
|
|
65
|
+
else
|
|
66
|
+
# missing item: autovivify item and add to collection
|
|
67
|
+
val = field.item_type.receive({ key_method => item_key, :owner => self }.merge(attrs), &block)
|
|
68
|
+
set_collection_item(plural_name, item_key, val)
|
|
69
|
+
end
|
|
70
|
+
val
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def get_collection_item(plural_name, item_key)
|
|
74
|
+
collection_of(plural_name)[item_key]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def set_collection_item(plural_name, item_key, item)
|
|
78
|
+
collection_of(plural_name)[item_key] = item
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def has_collection_item?(plural_name, item_key)
|
|
82
|
+
collection_of(plural_name).include?(item_key)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def key_method
|
|
86
|
+
:name
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def to_key
|
|
90
|
+
self.read_attribute(key_method)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def inspect_helper(detailed, attrs)
|
|
94
|
+
attrs.delete(:owner)
|
|
95
|
+
# detailed ? str : ([str[0..-2], " #{to_key}>"].join)
|
|
96
|
+
str = super(detailed, attrs)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def collection_of(plural_name)
|
|
100
|
+
self.read_attribute(plural_name)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
module ClassMethods
|
|
104
|
+
include Gorillib::Model::ClassMethods
|
|
105
|
+
|
|
106
|
+
def field(field_name, type, options={})
|
|
107
|
+
super(field_name, type, {:field_type => ::Gorillib::Builder::GetsetField}.merge(options))
|
|
108
|
+
end
|
|
109
|
+
def member(field_name, type, options={})
|
|
110
|
+
field(field_name, type, {:field_type => ::Gorillib::Builder::MemberField}.merge(options))
|
|
111
|
+
end
|
|
112
|
+
def collection(field_name, item_type, options={})
|
|
113
|
+
field(field_name, Gorillib::Collection, {
|
|
114
|
+
:item_type => item_type,
|
|
115
|
+
:field_type => ::Gorillib::Builder::CollectionField}.merge(options))
|
|
116
|
+
end
|
|
117
|
+
def simple_field(field_name, type, options={})
|
|
118
|
+
field(field_name, type, {:field_type => ::Gorillib::Model::Field}.merge(options))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
protected
|
|
122
|
+
|
|
123
|
+
def define_attribute_getset(field)
|
|
124
|
+
field_name = field.name; type = field.type
|
|
125
|
+
define_meta_module_method(field_name, field.visibility(:reader)) do |*args, &block|
|
|
126
|
+
begin
|
|
127
|
+
getset(field, *args, &block)
|
|
128
|
+
rescue StandardError => err ; err.polish("#{self.class}.#{field_name} type #{type} on #{args}'") rescue nil ; raise ; end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def define_member_getset(field)
|
|
133
|
+
field_name = field.name; type = field.type
|
|
134
|
+
define_meta_module_method(field_name, field.visibility(:reader)) do |*args, &block|
|
|
135
|
+
begin
|
|
136
|
+
getset_member(field, *args, &block)
|
|
137
|
+
rescue StandardError => err ; err.polish("#{self.class}.#{field_name} type #{type} on #{args}'") rescue nil ; raise ; end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def define_collection_getset(field)
|
|
142
|
+
field_name = field.name; item_type = field.item_type
|
|
143
|
+
define_meta_module_method(field.singular_name, field.visibility(:collection_getset)) do |*args, &block|
|
|
144
|
+
begin
|
|
145
|
+
getset_collection_item(field, *args, &block)
|
|
146
|
+
rescue StandardError => err ; err.polish("#{self.class}.#{field_name} c[#{item_type}] on #{args}'") rescue nil ; raise ; end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def define_collection_receiver(field)
|
|
151
|
+
plural_name = field.name; item_type = field.item_type; field_type = field.type
|
|
152
|
+
define_meta_module_method("receive_#{plural_name}", true) do |coll, &block|
|
|
153
|
+
begin
|
|
154
|
+
if coll.is_a?(field_type)
|
|
155
|
+
write_attribute(plural_name, coll)
|
|
156
|
+
else
|
|
157
|
+
read_attribute(plural_name).receive!(coll, &block)
|
|
158
|
+
end
|
|
159
|
+
self
|
|
160
|
+
rescue StandardError => err ; err.polish("#{self.class} #{plural_name} c[#{item_type}] on #{args}'") rescue nil ; raise ; end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def define_collection_tester(field)
|
|
165
|
+
plural_name = field.plural_name
|
|
166
|
+
define_meta_module_method("has_#{field.singular_name}?", field.visibility(:collection_tester)) do |item_key|
|
|
167
|
+
begin
|
|
168
|
+
collection_of(plural_name).include?(item_key)
|
|
169
|
+
rescue StandardError => err ; err.polish("#{self.class}.#{plural_name} having #{item_key}?'") rescue nil ; raise ; end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
module FancyBuilder
|
|
177
|
+
extend Gorillib::Concern
|
|
178
|
+
include Gorillib::Builder
|
|
179
|
+
|
|
180
|
+
included do |base|
|
|
181
|
+
base.field :name, Symbol
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
module ClassMethods
|
|
185
|
+
include Gorillib::Builder::ClassMethods
|
|
186
|
+
|
|
187
|
+
def belongs_to(field_name, type, options={})
|
|
188
|
+
field = field(field_name, type, {:field_type => ::Gorillib::Builder::MemberField }.merge(options))
|
|
189
|
+
define_meta_module_method "#{field.name}_name" do
|
|
190
|
+
val = getset_member(field) or return nil
|
|
191
|
+
val.name
|
|
192
|
+
end
|
|
193
|
+
field
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def option(field_name, options={})
|
|
197
|
+
type = options.delete(:type){ Whatever }
|
|
198
|
+
field(field_name, type, {:field_type => ::Gorillib::Builder::GetsetField }.merge(options))
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def collects(type, clxn_name)
|
|
202
|
+
type_handle = type.handle
|
|
203
|
+
define_meta_module_method type_handle do |item_name, attrs={}, options={}, &block|
|
|
204
|
+
send(clxn_name, item_name, attrs, options.merge(:factory => type), &block)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
module Builder
|
|
211
|
+
|
|
212
|
+
class GetsetField < Gorillib::Model::Field
|
|
213
|
+
self.visibilities = visibilities.merge(:writer => false, :tester => false, :getset => true)
|
|
214
|
+
def inscribe_methods(model)
|
|
215
|
+
model.__send__(:define_attribute_getset, self)
|
|
216
|
+
model.__send__(:define_attribute_writer, self.name, self.type, visibility(:writer))
|
|
217
|
+
model.__send__(:define_attribute_tester, self.name, self.type, visibility(:tester))
|
|
218
|
+
model.__send__(:define_attribute_receiver, self.name, self.type, visibility(:receiver))
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
class MemberField < Gorillib::Model::Field
|
|
223
|
+
self.visibilities = visibilities.merge(:writer => false, :tester => true)
|
|
224
|
+
def inscribe_methods(model)
|
|
225
|
+
model.__send__(:define_member_getset, self)
|
|
226
|
+
model.__send__(:define_attribute_writer, self.name, self.type, visibility(:writer))
|
|
227
|
+
model.__send__(:define_attribute_tester, self.name, self.type, visibility(:tester))
|
|
228
|
+
model.__send__(:define_attribute_receiver, self.name, self.type, visibility(:receiver))
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
class CollectionField < Gorillib::Model::Field
|
|
233
|
+
field :singular_name, Symbol, :default => ->{ Gorillib::Inflector.singularize(name.to_s).to_sym }
|
|
234
|
+
field :item_type, Class, :default => Whatever
|
|
235
|
+
|
|
236
|
+
self.visibilities = visibilities.merge(:writer => false, :tester => false,
|
|
237
|
+
:collection_getset => :public, :collection_tester => true)
|
|
238
|
+
|
|
239
|
+
alias_method :plural_name, :name
|
|
240
|
+
def singular_name
|
|
241
|
+
@singular_name ||= Gorillib::Inflector.singularize(name.to_s).to_sym
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def inscribe_methods(model)
|
|
245
|
+
item_type = self.item_type
|
|
246
|
+
self.default = ->{ Gorillib::Collection.new(item_type) }
|
|
247
|
+
raise "Plural and singular names must differ: #{self.plural_name}" if (singular_name == plural_name)
|
|
248
|
+
#
|
|
249
|
+
@visibilities[:writer] = false
|
|
250
|
+
model.__send__(:define_attribute_reader, self.name, self.type, visibility(:reader))
|
|
251
|
+
model.__send__(:define_attribute_tester, self.name, self.type, visibility(:tester))
|
|
252
|
+
#
|
|
253
|
+
model.__send__(:define_collection_receiver, self)
|
|
254
|
+
model.__send__(:define_collection_getset, self)
|
|
255
|
+
model.__send__(:define_collection_tester, self)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
end
|
|
260
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Gorillib
|
|
2
|
+
class Collection
|
|
3
|
+
module HasCollection
|
|
4
|
+
|
|
5
|
+
def has_collection(clxn_name, type, key_method=:name)
|
|
6
|
+
plural_name = clxn_name
|
|
7
|
+
singular_name = Gorillib::Inflector.singularize(clxn_name.to_s).to_sym
|
|
8
|
+
|
|
9
|
+
instance_variable_set("@#{plural_name}", Gorillib::Collection.new(type, key_method))
|
|
10
|
+
|
|
11
|
+
define_singleton_method(plural_name) do
|
|
12
|
+
instance_variable_get("@#{plural_name}") if instance_variable_defined?("@#{plural_name}")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
define_singleton_method(singular_name) do |item_key, attrs={}, options={}, &block|
|
|
16
|
+
collection = instance_variable_get("@#{clxn_name}")
|
|
17
|
+
val = collection.fetch(item_key) do
|
|
18
|
+
attrs.merge!(key_method => item_key, :owner => self) if attrs.respond_to?(:merge!)
|
|
19
|
+
factory = options.fetch(:factory){ type }
|
|
20
|
+
new_val = factory.receive(attrs)
|
|
21
|
+
collection << new_val
|
|
22
|
+
new_val
|
|
23
|
+
end
|
|
24
|
+
val.instance_exec(&block) if block
|
|
25
|
+
val
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
require 'gorillib/metaprogramming/delegation'
|
|
2
|
+
require 'gorillib/metaprogramming/class_attribute'
|
|
3
|
+
|
|
4
|
+
module Gorillib
|
|
5
|
+
class Collection
|
|
6
|
+
# [String, Symbol] Method invoked on a new item to generate its collection key; :to_key by default
|
|
7
|
+
attr_accessor :key_method
|
|
8
|
+
# The default `key_method` invoked on a new item to generate its collection key
|
|
9
|
+
DEFAULT_KEY_METHOD = :to_key
|
|
10
|
+
|
|
11
|
+
# [Class, #receive] Factory for generating a new collection item.
|
|
12
|
+
class_attribute :factory, :instance_writer => false
|
|
13
|
+
singleton_class.class_eval{ protected :factory= }
|
|
14
|
+
|
|
15
|
+
# [{Symbol => Object}] The actual store of items, but not for you to mess with
|
|
16
|
+
attr_reader :clxn
|
|
17
|
+
protected :clxn
|
|
18
|
+
|
|
19
|
+
delegate :[], :[]=, :delete, :fetch, :to => :clxn
|
|
20
|
+
delegate :keys, :values, :each_pair, :each_value, :to => :clxn
|
|
21
|
+
delegate :has_key?, :include?, :length, :size, :empty?, :blank?, :to => :clxn
|
|
22
|
+
|
|
23
|
+
def initialize(factory=nil, key_method=DEFAULT_KEY_METHOD)
|
|
24
|
+
@key_method = key_method
|
|
25
|
+
@factory = factory unless factory.nil?
|
|
26
|
+
@clxn = {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [Array] an array holding the items
|
|
30
|
+
def to_a ; values ; end
|
|
31
|
+
# @return [{Symbol => Object}] a hash of key=>item pairs
|
|
32
|
+
def to_hash ; clxn.dup ; end
|
|
33
|
+
|
|
34
|
+
# Merge the new items in-place; given items clobber existing items
|
|
35
|
+
# @param other [{Symbol => Object}, Array<Object>] a hash of key=>item pairs or a list of items
|
|
36
|
+
# @return [Gorillib::Collection] the collection
|
|
37
|
+
def merge!(other)
|
|
38
|
+
clxn.merge!( convert_collection(other) )
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
alias_method :concat, :merge!
|
|
42
|
+
alias_method :receive!, :merge!
|
|
43
|
+
|
|
44
|
+
def self.receive(items, *args)
|
|
45
|
+
coll = new(*args)
|
|
46
|
+
coll.receive!(items)
|
|
47
|
+
coll
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Two collections are equal if they have the same class and their contents are equal
|
|
51
|
+
#
|
|
52
|
+
# @param [Gorillib::Collection, Object] other The other collection to compare
|
|
53
|
+
# @return [true, false] True if attributes are equal and other is instance of the same Class
|
|
54
|
+
def ==(other)
|
|
55
|
+
return false unless other.instance_of?(self.class)
|
|
56
|
+
clxn == other.send(:clxn)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Merge the new items into a new collection; given items clobber existing items
|
|
60
|
+
# @param other [{Symbol => Object}, Array<Object>] a hash of key=>item pairs or a list of items
|
|
61
|
+
# @return [Gorillib::Collection] a new merged collection
|
|
62
|
+
def merge(other)
|
|
63
|
+
dup.merge!(other)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def create(*args)
|
|
67
|
+
item = factory.receive(*args)
|
|
68
|
+
self << item
|
|
69
|
+
item
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def find_or_create(key)
|
|
73
|
+
fetch(key){ create(key_method => key) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Adds an item in-place
|
|
77
|
+
# @return [Gorillib::Collection] the collection
|
|
78
|
+
def <<(val)
|
|
79
|
+
merge! [val]
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [String] string describing the collection's array representation
|
|
84
|
+
def to_s ; to_a.to_s ; end
|
|
85
|
+
# @return [String] string describing the collection's array representation
|
|
86
|
+
def inspect(detailed=true)
|
|
87
|
+
str = "c{ "
|
|
88
|
+
if detailed
|
|
89
|
+
str << clxn.map do |key, val|
|
|
90
|
+
"%-15s %s" % ["#{key}:", val.inspect]
|
|
91
|
+
end.join(",\n ")
|
|
92
|
+
else
|
|
93
|
+
str << keys.join(", ")
|
|
94
|
+
end
|
|
95
|
+
str << " }"
|
|
96
|
+
end
|
|
97
|
+
# @return [Array] serializable array representation of the collection
|
|
98
|
+
def to_wire(options)
|
|
99
|
+
to_a.map{|el| el.respond_to?(:to_wire) ? el.to_wire(options) : el }
|
|
100
|
+
end
|
|
101
|
+
alias_method(:as_json, :to_wire)
|
|
102
|
+
# @return [String] JSON serialization of the collection's array representation
|
|
103
|
+
def to_json(*args)
|
|
104
|
+
p [self, options]
|
|
105
|
+
to_wire(*args).to_json(*args)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
protected
|
|
109
|
+
|
|
110
|
+
def convert_value(val)
|
|
111
|
+
return val unless factory
|
|
112
|
+
return nil if val.nil?
|
|
113
|
+
factory.receive(val)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# - if the given collection responds_to `to_hash`, it is merged into the internal collection; each hash key *must* match the id of its value or results are undefined.
|
|
117
|
+
# - otherwise, it merges a hash generates from the id/value pairs of each object in the given collection.
|
|
118
|
+
def convert_collection(cc)
|
|
119
|
+
return cc.to_hash if cc.respond_to?(:to_hash)
|
|
120
|
+
cc.inject({}) do |acc, val|
|
|
121
|
+
val = convert_value(val)
|
|
122
|
+
key = val.public_send(key_method).to_sym
|
|
123
|
+
acc[key] = val
|
|
124
|
+
acc
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'configliere'
|
|
2
|
+
require 'gorillib/builder'
|
|
3
|
+
require 'gorillib/string/inflections'
|
|
4
|
+
|
|
5
|
+
module Gorillib
|
|
6
|
+
module Configurable
|
|
7
|
+
extend Gorillib::Concern
|
|
8
|
+
include Gorillib::Builder
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def receive(attrs = {}, &blk)
|
|
12
|
+
conf = settings.load_configuration_in_order!(configuration_scope.to_s)
|
|
13
|
+
super(attrs.merge(conf), &blk)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def config(name, type, options = {})
|
|
17
|
+
field(name, type, options)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
included do
|
|
22
|
+
self.class_attribute(:configuration_scope, :settings)
|
|
23
|
+
self.configuration_scope = self.to_s.underscore.to_sym
|
|
24
|
+
self.settings = Configliere::Param.new.use(:commandline, :config_file)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Kernel
|
|
2
|
+
def assert(value, message="Assertion failed", error=StandardError)
|
|
3
|
+
raise error, message, caller unless value
|
|
4
|
+
end
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class NullObject
|
|
8
|
+
def method_missing(*args, &block)
|
|
9
|
+
self
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def nil?; true; end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def Maybe(value)
|
|
16
|
+
value.nil? ? NullObject.new : value
|
|
17
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Exception.class_eval do
|
|
2
|
+
# @return [Array] __FILE__, __LINE__, description
|
|
3
|
+
def self.caller_parts
|
|
4
|
+
caller_line = caller[1]
|
|
5
|
+
mg = %r{\A([^:]+):(\d+):in \`([^\']+)\'\z}.match(caller_line) or return [caller_line, 1, 'unknown']
|
|
6
|
+
[mg[1], mg[2].to_i, mg[3]]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# @note !! Be sure to rescue the call to this method; few things suck worse than debugging your rescue blocks/
|
|
11
|
+
def polish(extra_info)
|
|
12
|
+
filename, _, method_name = self.class.caller_parts
|
|
13
|
+
method_name.gsub!(/rescue in /, '')
|
|
14
|
+
most_recent_line = backtrace.detect{|line| line.include?(filename) && line.include?(method_name) }
|
|
15
|
+
most_recent_line.sub!(/'$/, " for #{extra_info}'"[0..300])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
ArgumentError.class_eval do
|
|
21
|
+
# Raise an error just like Ruby's native message if the array of arguments
|
|
22
|
+
# doesn't match the expected length or range of lengths.
|
|
23
|
+
#
|
|
24
|
+
# @example want `getset(:foo)` to be different from `getset(:foo, nil)`
|
|
25
|
+
# def getset(key, *args)
|
|
26
|
+
# ArgumentError.check_arity!(args, 0..1)
|
|
27
|
+
# return self[key] if args.empty?
|
|
28
|
+
# self[key] = args.first
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# @overload check_arity!(args, n)
|
|
32
|
+
# @param [Array] args splat args as handed to the caller
|
|
33
|
+
# @param [Integer] val expected length
|
|
34
|
+
# @overload check_arity!(args, x..y)
|
|
35
|
+
# @param [Array] args splat args as handed to the caller
|
|
36
|
+
# @param [#include?] val expected range/list/set of lengths
|
|
37
|
+
# @raise ArgumentError when there are
|
|
38
|
+
def self.check_arity!(args, val)
|
|
39
|
+
allowed_arity = val.is_a?(Integer) ? (val..val) : val
|
|
40
|
+
return true if allowed_arity.include?(args.length)
|
|
41
|
+
raise self.new("wrong number of arguments (#{args.length} for #{val})")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Raise an error just like Ruby's native message if there are fewer arguments
|
|
45
|
+
# than expected
|
|
46
|
+
#
|
|
47
|
+
# @example want to use splat args, requiring at least one
|
|
48
|
+
# def assemble_path(*pathsegs)
|
|
49
|
+
# ArgumentError.arity_at_least!(pathsegs, 1)
|
|
50
|
+
# # ...
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# @param [Array] args splat args as handed to the caller
|
|
54
|
+
# @param [Integer] val minimum expected length
|
|
55
|
+
def self.arity_at_least!(args, min_arity)
|
|
56
|
+
check_arity!(args, min_arity .. Float::INFINITY)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
NoMethodError.class_eval do
|
|
61
|
+
MESSAGE = "undefined method `%s' for %s:%s"
|
|
62
|
+
|
|
63
|
+
def self.undefined_method(obj)
|
|
64
|
+
file, line, meth = caller_parts
|
|
65
|
+
self.new(MESSAGE % [meth, obj, obj.class])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.unimplemented_method(obj)
|
|
69
|
+
file, line, meth = caller_parts
|
|
70
|
+
self.new("#{MESSAGE} -- not implemented yet" % [meth, obj, obj.class])
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.abstract(obj)
|
|
74
|
+
file, line, meth = caller_parts
|
|
75
|
+
self.new("#{MESSAGE} -- must be implemented by the subclass" % [meth, obj, obj.class])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
end
|