gorillib 0.1.11 → 0.4.0pre
Sign up to get free protection for your applications and to get access to all the features.
- 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,372 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'gorillib/type/extended'
|
3
|
+
|
4
|
+
def Gorillib::Factory(*args)
|
5
|
+
::Gorillib::Factory.receive(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
module Gorillib
|
9
|
+
|
10
|
+
module Factory
|
11
|
+
class FactoryMismatchError < ArgumentError ; end
|
12
|
+
|
13
|
+
def self.receive(type)
|
14
|
+
case
|
15
|
+
when type.is_a?(Proc) || type.is_a?(Method) then return Gorillib::Factory::ApplyProcFactory.new(type)
|
16
|
+
when type.respond_to?(:receive) then return type
|
17
|
+
when factories.include?(type) then return factories[type]
|
18
|
+
when type.is_a?(String) then
|
19
|
+
return Gorillib::Inflector.constantize(Gorillib::Inflector.camelize(type.gsub(/\./, '/')))
|
20
|
+
else raise ArgumentError, "Don't know which factory makes a #{type}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def self.factories
|
26
|
+
@factories ||= Gorillib::Collection.new.tap{|f| f.key_method = :name }
|
27
|
+
end
|
28
|
+
public
|
29
|
+
|
30
|
+
def self.register_factory(factory, *typenames)
|
31
|
+
if typenames.blank?
|
32
|
+
typenames = [factory.typename, factory.product]
|
33
|
+
end
|
34
|
+
typenames.each{|typename| factories[typename] = factory }
|
35
|
+
end
|
36
|
+
|
37
|
+
class BaseFactory
|
38
|
+
# [Class] The type of objects produced by this factory
|
39
|
+
class_attribute :product
|
40
|
+
|
41
|
+
# [Array<Symbol>] methods that can be redefined by passing a block to an
|
42
|
+
# instance No not add to your superclass' value in-place; instead, use
|
43
|
+
# `self.redefinable_methods += [...]`.
|
44
|
+
# @see #redefine
|
45
|
+
class_attribute :redefinable_methods, :instance_writer => false
|
46
|
+
self.redefinable_methods = Set.new([:blankish?, :convert])
|
47
|
+
|
48
|
+
# [Array<Object>] objects considered to be equivalent to `nil`
|
49
|
+
class_attribute :blankish_vals
|
50
|
+
self.blankish_vals = Set.new([ nil, "" ]) # note: [] {} and false are NOT blankish by default
|
51
|
+
|
52
|
+
def initialize(options={})
|
53
|
+
@product = options.delete(:product) if options.has_key?(:product)
|
54
|
+
@blankish_vals = options.delete(:blankish_vals) if options.has_key?(:blankish_vals)
|
55
|
+
options.extract!(*redefinable_methods).each do |meth, value_or_block|
|
56
|
+
redefine(meth, value_or_block)
|
57
|
+
end
|
58
|
+
warn "Unknown options #{options.keys}" unless options.empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.typename
|
62
|
+
Gorillib::Inflector.underscore(product.name).to_sym
|
63
|
+
end
|
64
|
+
def typename ; self.class.typename ; end
|
65
|
+
|
66
|
+
def self.receive(*args, &block)
|
67
|
+
self.new.receive(*args, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
# A `native` object does not need any transformation; it is accepted directly.
|
71
|
+
# By default, an object is native if it `is_a?(product)`
|
72
|
+
#
|
73
|
+
# @param [Object] obj the object to convert and receive
|
74
|
+
# @return [true, false] true if the item does not need conversion
|
75
|
+
def native?(obj)
|
76
|
+
obj.is_a?(product)
|
77
|
+
end
|
78
|
+
def self.native?(obj) self.new.native?(obj) ; end
|
79
|
+
|
80
|
+
# A `blankish` object should be converted to `nil`, not a value
|
81
|
+
#
|
82
|
+
# @param [Object] obj the object to convert and receive
|
83
|
+
# @return [true, false] true if the item is equivalent to a nil value
|
84
|
+
def blankish?(obj)
|
85
|
+
blankish_vals.include?(obj)
|
86
|
+
end
|
87
|
+
def self.blankish?(obj) self.new.blankish?(obj) ; end
|
88
|
+
|
89
|
+
def redefine(meth, *args, &block)
|
90
|
+
raise ArgumentError, "Cannot redefine #{meth} -- only #{redefinable_methods.inspect} are redefinable" unless redefinable_methods.include?(meth)
|
91
|
+
if args.present?
|
92
|
+
val = args.first
|
93
|
+
case
|
94
|
+
when block_given? then raise ArgumentError, "Pass a block or a value, not both"
|
95
|
+
when val.is_a?(Proc) || val.is_a?(Method) then block = val
|
96
|
+
else block = ->(*){ val.try_dup }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
define_singleton_method(meth, &block)
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
# Raises a FactoryMismatchError.
|
106
|
+
def mismatched!(obj, message=nil, *args)
|
107
|
+
message ||= "item cannot be converted to #{product}"
|
108
|
+
message << (" got #{obj.inspect}" rescue ' (and is uninspectable)')
|
109
|
+
raise FactoryMismatchError, message, *args
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.register_factory!(*args)
|
113
|
+
Gorillib::Factory.register_factory(self, *args)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class ConvertingFactory < BaseFactory
|
118
|
+
def receive(obj)
|
119
|
+
return nil if blankish?(obj)
|
120
|
+
return obj if native?(obj)
|
121
|
+
convert(obj)
|
122
|
+
rescue NoMethodError, TypeError, RangeError => err
|
123
|
+
mismatched!(obj, err.message, err.backtrace)
|
124
|
+
end
|
125
|
+
protected
|
126
|
+
# Convert a receivable object to the factory's product type. This method
|
127
|
+
# should convert an object to `native?` form or die trying; any
|
128
|
+
# polymorphism (such as converting an empty string to nil) happens in
|
129
|
+
# other methods called by `receive`.
|
130
|
+
#
|
131
|
+
# @param [Object] obj the object to convert.
|
132
|
+
def convert(obj)
|
133
|
+
obj.dup
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# A NonConvertingFactory accepts objects that are *already* native, and
|
139
|
+
# throws a mismatch error for anything else.
|
140
|
+
#
|
141
|
+
# @example
|
142
|
+
# ff = Gorillib::Factory::NonConvertingFactory.new(:product => String, :blankish_vals => [nil])
|
143
|
+
# ff.receive(nil) #=> nil
|
144
|
+
# ff.receive("bob") #=> "bob"
|
145
|
+
# ff.receive(:bob) #=> Gorillib::Factory::FactoryMismatchError: must be an instance of String, got 3
|
146
|
+
#
|
147
|
+
class NonConvertingFactory < BaseFactory
|
148
|
+
self.blankish_vals = [nil]
|
149
|
+
|
150
|
+
def receive(obj)
|
151
|
+
return nil if blankish?(obj)
|
152
|
+
return obj if native?(obj)
|
153
|
+
mismatched!(obj, "must be an instance of #{product},")
|
154
|
+
rescue NoMethodError => err
|
155
|
+
mismatched!(obj, err.message, err.backtrace)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class IdenticalFactory < BaseFactory
|
160
|
+
self.redefinable_methods = []
|
161
|
+
self.blankish_vals = []
|
162
|
+
def native?(obj) true ; end
|
163
|
+
def blankish?(obj) false ; end
|
164
|
+
def receive(obj)
|
165
|
+
obj
|
166
|
+
end
|
167
|
+
register_factory!(:identical, :whatever)
|
168
|
+
end
|
169
|
+
::Whatever = IdenticalFactory unless defined?(Whatever)
|
170
|
+
|
171
|
+
# __________________________________________________________________________
|
172
|
+
#
|
173
|
+
# Concrete Factories
|
174
|
+
# __________________________________________________________________________
|
175
|
+
|
176
|
+
class StringFactory < ConvertingFactory
|
177
|
+
self.product = String
|
178
|
+
self.blankish_vals -= [""]
|
179
|
+
def native?(obj) obj.respond_to?(:to_str) end
|
180
|
+
def convert(obj) obj.to_s end
|
181
|
+
register_factory!
|
182
|
+
end
|
183
|
+
|
184
|
+
class GuidFactory < StringFactory ; self.product = ::Guid ; register_factory! ; end
|
185
|
+
class HostnameFactory < StringFactory ; self.product = ::Hostname ; register_factory! ; end
|
186
|
+
class IpAddressFactory < StringFactory ; self.product = ::IpAddress ; register_factory! ; end
|
187
|
+
|
188
|
+
class BinaryFactory < StringFactory
|
189
|
+
def convert(obj)
|
190
|
+
super.force_encoding("BINARY")
|
191
|
+
end
|
192
|
+
register_factory!(:binary)
|
193
|
+
end
|
194
|
+
|
195
|
+
class PathnameFactory < ConvertingFactory
|
196
|
+
self.product = ::Pathname
|
197
|
+
def convert(obj) Pathname.new(obj) end
|
198
|
+
register_factory!
|
199
|
+
end
|
200
|
+
|
201
|
+
class SymbolFactory < ConvertingFactory
|
202
|
+
self.product = Symbol
|
203
|
+
def convert(obj) obj.to_sym end
|
204
|
+
register_factory!
|
205
|
+
end
|
206
|
+
|
207
|
+
class RegexpFactory < ConvertingFactory
|
208
|
+
self.product = Regexp
|
209
|
+
def convert(obj) Regexp.new(obj) end
|
210
|
+
register_factory!
|
211
|
+
end
|
212
|
+
|
213
|
+
class IntegerFactory < ConvertingFactory
|
214
|
+
self.product = Integer
|
215
|
+
def convert(obj) obj.to_i end
|
216
|
+
register_factory!(:int, :integer, Integer)
|
217
|
+
end
|
218
|
+
class BignumFactory < IntegerFactory
|
219
|
+
self.product = Bignum
|
220
|
+
register_factory!
|
221
|
+
end
|
222
|
+
class FloatFactory < ConvertingFactory
|
223
|
+
self.product = Float
|
224
|
+
def convert(obj) obj.to_f end
|
225
|
+
register_factory!
|
226
|
+
end
|
227
|
+
class ComplexFactory < ConvertingFactory
|
228
|
+
self.product = Complex
|
229
|
+
def convert(obj) obj.to_c end
|
230
|
+
register_factory!
|
231
|
+
end
|
232
|
+
class RationalFactory < ConvertingFactory
|
233
|
+
self.product = Rational
|
234
|
+
def convert(obj) obj.to_r end
|
235
|
+
register_factory!
|
236
|
+
end
|
237
|
+
|
238
|
+
class TimeFactory < ConvertingFactory
|
239
|
+
self.product = Time
|
240
|
+
def convert(obj)
|
241
|
+
case obj
|
242
|
+
when String
|
243
|
+
Time.parse(obj).utc
|
244
|
+
when Numeric
|
245
|
+
Time.at(obj).utc
|
246
|
+
end
|
247
|
+
rescue ArgumentError => err
|
248
|
+
warn "Cannot parse time #{obj}: #{err}"
|
249
|
+
return nil
|
250
|
+
end
|
251
|
+
register_factory!
|
252
|
+
end
|
253
|
+
|
254
|
+
# __________________________________________________________________________
|
255
|
+
|
256
|
+
class ClassFactory < NonConvertingFactory ; self.product = Class ; register_factory! ; end
|
257
|
+
class ModuleFactory < NonConvertingFactory ; self.product = Module ; register_factory! ; end
|
258
|
+
class TrueFactory < NonConvertingFactory ; self.product = TrueClass ; register_factory!(:true, TrueClass) ; end
|
259
|
+
class FalseFactory < NonConvertingFactory ; self.product = FalseClass ; register_factory!(:false, FalseClass) ; end
|
260
|
+
|
261
|
+
class ExceptionFactory < NonConvertingFactory ; self.product = Exception ; register_factory!(:exception, Exception) ; end
|
262
|
+
|
263
|
+
class NilFactory < NonConvertingFactory
|
264
|
+
self.product = NilClass
|
265
|
+
self.blankish_vals = []
|
266
|
+
register_factory!(:nil, NilClass)
|
267
|
+
end
|
268
|
+
|
269
|
+
class BooleanFactory < ConvertingFactory
|
270
|
+
self.product = [TrueClass, FalseClass]
|
271
|
+
self.blankish_vals = [nil]
|
272
|
+
def native?(obj) obj.equal?(true) || obj.equal?(false) ; end
|
273
|
+
def convert(obj) (obj.to_s == "false") ? false : true ; end
|
274
|
+
register_factory!(:boolean)
|
275
|
+
def self.typename() :boolean ; end
|
276
|
+
end
|
277
|
+
|
278
|
+
#
|
279
|
+
#
|
280
|
+
#
|
281
|
+
|
282
|
+
class EnumerableFactory < ConvertingFactory
|
283
|
+
# [#receive] factory for converting items
|
284
|
+
attr_reader :items_factory
|
285
|
+
self.redefinable_methods += [:empty_product]
|
286
|
+
self.blankish_vals = Set.new([ nil ])
|
287
|
+
|
288
|
+
def initialize(options={})
|
289
|
+
@items_factory = Gorillib::Factory.receive( options.delete(:items){ IdenticalFactory.new } )
|
290
|
+
super(options)
|
291
|
+
end
|
292
|
+
|
293
|
+
def native?(obj)
|
294
|
+
false
|
295
|
+
end
|
296
|
+
|
297
|
+
def empty_product
|
298
|
+
product.new
|
299
|
+
end
|
300
|
+
|
301
|
+
def convert(obj)
|
302
|
+
clxn = empty_product
|
303
|
+
obj.each do |val|
|
304
|
+
clxn << items_factory.receive(val)
|
305
|
+
end
|
306
|
+
clxn
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
class ArrayFactory < EnumerableFactory
|
311
|
+
self.product = Array
|
312
|
+
register_factory!
|
313
|
+
end
|
314
|
+
|
315
|
+
class SetFactory < EnumerableFactory
|
316
|
+
self.product = Set
|
317
|
+
register_factory!
|
318
|
+
end
|
319
|
+
|
320
|
+
class HashFactory < EnumerableFactory
|
321
|
+
# [#receive] factory for converting keys
|
322
|
+
attr_reader :keys_factory
|
323
|
+
self.product = Hash
|
324
|
+
|
325
|
+
def initialize(options={})
|
326
|
+
@keys_factory = Gorillib::Factory( options.delete(:keys){ Whatever.new } )
|
327
|
+
super(options)
|
328
|
+
end
|
329
|
+
|
330
|
+
def convert(obj)
|
331
|
+
hsh = empty_product
|
332
|
+
obj.each_pair do |key, val|
|
333
|
+
hsh[keys_factory.receive(key)] = items_factory.receive(val)
|
334
|
+
end
|
335
|
+
hsh
|
336
|
+
end
|
337
|
+
register_factory!
|
338
|
+
end
|
339
|
+
|
340
|
+
class RangeFactory < NonConvertingFactory
|
341
|
+
self.product = Range
|
342
|
+
self.blankish_vals = [ nil, [] ]
|
343
|
+
register_factory!
|
344
|
+
end
|
345
|
+
|
346
|
+
# __________________________________________________________________________
|
347
|
+
|
348
|
+
class ApplyProcFactory < ConvertingFactory
|
349
|
+
attr_reader :callable
|
350
|
+
self.blankish_vals = Set.new([nil])
|
351
|
+
|
352
|
+
def initialize(callable=nil, options={}, &block)
|
353
|
+
if block_given?
|
354
|
+
raise ArgumentError, "Pass a block or a value, not both" unless callable.nil?
|
355
|
+
callable = block
|
356
|
+
end
|
357
|
+
@callable = callable
|
358
|
+
super(options)
|
359
|
+
end
|
360
|
+
def native?(val)
|
361
|
+
false
|
362
|
+
end
|
363
|
+
def convert(obj)
|
364
|
+
callable.call(obj)
|
365
|
+
end
|
366
|
+
register_factory!(:proc)
|
367
|
+
end
|
368
|
+
|
369
|
+
|
370
|
+
end
|
371
|
+
|
372
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Gorillib
|
2
|
+
module Model
|
3
|
+
|
4
|
+
# Represents a field for reflection
|
5
|
+
#
|
6
|
+
# @example Usage
|
7
|
+
# Gorillib::Model::Field.new(:name => 'problems', type => Integer, :doc => 'Count of problems')
|
8
|
+
#
|
9
|
+
#
|
10
|
+
class Field
|
11
|
+
include Gorillib::Model
|
12
|
+
remove_possible_method(:type)
|
13
|
+
|
14
|
+
# [Gorillib::Model] Model owning this field
|
15
|
+
attr_reader :model
|
16
|
+
|
17
|
+
# [Hash] all options passed to the field not recognized by one of its own current fields
|
18
|
+
attr_reader :extra_attributes
|
19
|
+
|
20
|
+
# Note: `Gorillib::Model::Field` is assembled in two pieces, so that it
|
21
|
+
# can behave as a model itself. Defining `name` here, along with some
|
22
|
+
# fudge in #initialize, provides enough functionality to bootstrap.
|
23
|
+
# The fields are then defined properly at the end of the file.
|
24
|
+
|
25
|
+
attr_reader :name
|
26
|
+
attr_reader :type
|
27
|
+
|
28
|
+
class_attribute :visibilities, :instance_writer => false
|
29
|
+
self.visibilities = { :reader => :public, :writer => :public, :receiver => :public, :tester => false }
|
30
|
+
|
31
|
+
|
32
|
+
# @param [#to_sym] name Field name
|
33
|
+
# @param [#receive] type Factory for field values. To accept any object as-is, specify `Object` as the type.
|
34
|
+
# @param [Gorillib::Model] model Field's owner
|
35
|
+
# @param [Hash{Symbol => Object}] options Extended attributes
|
36
|
+
# @option options [String] doc Description of the field's purpose
|
37
|
+
# @option options [true, false, :public, :protected, :private] :reader Visibility for the reader (`#foo`) method; `false` means don't create one.
|
38
|
+
# @option options [true, false, :public, :protected, :private] :writer Visibility for the writer (`#foo=`) method; `false` means don't create one.
|
39
|
+
# @option options [true, false, :public, :protected, :private] :receiver Visibility for the receiver (`#receive_foo`) method; `false` means don't create one.
|
40
|
+
#
|
41
|
+
def initialize(name, type, model, options={})
|
42
|
+
Validate.identifier!(name)
|
43
|
+
@model = model
|
44
|
+
@name = name.to_sym
|
45
|
+
@type = self.factory_for(type)
|
46
|
+
default_visabilities = visibilities
|
47
|
+
@visibilities = default_visabilities.merge( options.extract!(*default_visabilities.keys) )
|
48
|
+
@doc = options.delete(:name){ "#{name} field" }
|
49
|
+
receive!(options)
|
50
|
+
end
|
51
|
+
|
52
|
+
# __________________________________________________________________________
|
53
|
+
|
54
|
+
# @return [String] the field name
|
55
|
+
def to_s
|
56
|
+
name.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def factory_for(type)
|
60
|
+
Gorillib::Factory(type)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [String] Human-readable presentation of the field definition
|
64
|
+
def inspect
|
65
|
+
args = [name.inspect, type.to_s]
|
66
|
+
"field(#{args.join(", ")})"
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_hash
|
70
|
+
attributes.merge!(@visibility).merge!(@extra_attributes)
|
71
|
+
end
|
72
|
+
|
73
|
+
def ==(val)
|
74
|
+
super && (val.extra_attributes == self.extra_attributes) && (val.model == self.model)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.receive(hsh)
|
78
|
+
name = hsh.fetch(:name)
|
79
|
+
type = hsh.fetch(:type)
|
80
|
+
model = hsh.fetch(:model)
|
81
|
+
new(name, type, model, hsh)
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# returns the visibility
|
86
|
+
#
|
87
|
+
# @example reader is protected, no writer:
|
88
|
+
# Foo.field :granuloxity, :reader => :protected, :writer => false
|
89
|
+
#
|
90
|
+
def visibility(meth_type)
|
91
|
+
Validate.included_in!("method type", meth_type, @visibilities.keys)
|
92
|
+
@visibilities[meth_type]
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
#
|
98
|
+
#
|
99
|
+
#
|
100
|
+
def inscribe_methods(model)
|
101
|
+
model.__send__(:define_attribute_reader, self.name, self.type, visibility(:reader))
|
102
|
+
model.__send__(:define_attribute_writer, self.name, self.type, visibility(:writer))
|
103
|
+
model.__send__(:define_attribute_tester, self.name, self.type, visibility(:tester))
|
104
|
+
model.__send__(:define_attribute_receiver, self.name, self.type, visibility(:receiver))
|
105
|
+
end
|
106
|
+
|
107
|
+
public
|
108
|
+
|
109
|
+
#
|
110
|
+
# Now we can construct the actual fields.
|
111
|
+
#
|
112
|
+
|
113
|
+
# Name of this field. Must start with `[A-Za-z_]` and subsequently contain only `[A-Za-z0-9_]` (required)
|
114
|
+
# @macro [attach] field
|
115
|
+
# @attribute $1
|
116
|
+
# @return [$2] the $1 field $*
|
117
|
+
field :name, String, :writer => false, :doc => "The field name. Must start with `[A-Za-z_]` and subsequently contain only `[A-Za-z0-9_]` (required)"
|
118
|
+
|
119
|
+
# Factory for the field's values
|
120
|
+
field :type, Class
|
121
|
+
|
122
|
+
# Field's description
|
123
|
+
field :doc, String
|
124
|
+
|
125
|
+
# remove the attr_reader method (needed for scaffolding), leaving the meta_module method to remain
|
126
|
+
remove_possible_method(:name)
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# * aliases
|
133
|
+
# * order
|
134
|
+
# * dirty
|
135
|
+
# * lazy
|
136
|
+
# * mass assignable
|
137
|
+
# * identifier / index
|
138
|
+
# * hook
|
139
|
+
# * validates / required
|
140
|
+
# - presence => true
|
141
|
+
# - uniqueness => true
|
142
|
+
# - numericality => true # also :==, :>, :>=, :<, :<=, :odd?, :even?, :equal_to, :less_than, etc
|
143
|
+
# - length => { :< => 7 } # also :==, :>=, :<=, :is, :minimum, :maximum
|
144
|
+
# - format => { :with => /.*/ }
|
145
|
+
# - inclusion => { :in => [1,2,3] }
|
146
|
+
# - exclusion => { :in => [1,2,3] }
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Gorillib
|
2
|
+
module Model
|
3
|
+
module NamedSchema
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
#
|
8
|
+
# Returns the meta_module -- a module extending the type, on which all the
|
9
|
+
# model methods are inscribed. This allows you to override the model methods
|
10
|
+
# and call +super()+ to get the generic behavior.
|
11
|
+
#
|
12
|
+
# The meta_module is named for the including class, but with 'Meta::'
|
13
|
+
# prepended and 'Type' appended -- so Geo::Place has meta_module
|
14
|
+
# "Meta::Geo::PlaceType"
|
15
|
+
#
|
16
|
+
def meta_module
|
17
|
+
return @_meta_module if defined?(@_meta_module)
|
18
|
+
if self.name
|
19
|
+
@_meta_module = ::Gorillib::Model::NamedSchema.get_nested_module("Meta::#{self.name}Type")
|
20
|
+
else
|
21
|
+
@_meta_module = Module.new
|
22
|
+
end
|
23
|
+
self.class_eval{ include(@_meta_module) }
|
24
|
+
@_meta_module
|
25
|
+
end
|
26
|
+
|
27
|
+
def define_meta_module_method(method_name, visibility=:public, &block)
|
28
|
+
if (visibility == false) then return ; end
|
29
|
+
if (visibility == true) then visibility = :public ; end
|
30
|
+
Validate.included_in!("visibility", visibility, [:public, :private, :protected])
|
31
|
+
meta_module.module_eval{ define_method(method_name, &block) }
|
32
|
+
meta_module.module_eval "#{visibility} :#{method_name}", __FILE__, __LINE__
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a module for the given names, rooted at Object (so
|
36
|
+
# implicity with '::').
|
37
|
+
# @example
|
38
|
+
# get_nested_module(["This", "That", "TheOther"])
|
39
|
+
# # This::That::TheOther
|
40
|
+
def self.get_nested_module(name)
|
41
|
+
name.split('::').inject(Object) do |parent_module, module_name|
|
42
|
+
# inherit = false makes these methods be scoped to parent_module instead of universally
|
43
|
+
if parent_module.const_defined?(module_name, false)
|
44
|
+
parent_module.const_get(module_name, false)
|
45
|
+
else
|
46
|
+
parent_module.const_set(module_name.to_sym, Module.new)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
File without changes
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Gorillib
|
2
|
+
module Model
|
3
|
+
|
4
|
+
def to_wire(options={})
|
5
|
+
attributes.merge(:_type => self.class.typename).inject({}) do |acc, (key,attr)|
|
6
|
+
acc[key] = attr.respond_to?(:to_wire) ? attr.to_wire(options) : attr
|
7
|
+
acc
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_json(options={})
|
12
|
+
MultiJson.dump(to_wire(options), options)
|
13
|
+
end
|
14
|
+
alias_method(:as_json, :to_wire)
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def from_tuple(*vals)
|
18
|
+
receive Hash[field_names[0..vals.length].zip(vals)]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Gorillib
|
2
|
+
module Model
|
3
|
+
module Validate
|
4
|
+
module_function
|
5
|
+
|
6
|
+
VALID_NAME_RE = /\A[A-Za-z_][A-Za-z0-9_]*\z/
|
7
|
+
def identifier!(name)
|
8
|
+
raise TypeError, "can't convert #{name.class} into Symbol", caller unless name.respond_to? :to_sym
|
9
|
+
raise ArgumentError, "Name must start with [A-Za-z_] and subsequently contain only [A-Za-z0-9_]", caller unless name =~ VALID_NAME_RE
|
10
|
+
end
|
11
|
+
|
12
|
+
def hashlike!(desc, val)
|
13
|
+
return true if val.respond_to?(:[]) && val.respond_to?(:has_key?)
|
14
|
+
raise ArgumentError, "#{desc} should be something that behaves like a hash: #{val.inspect}", caller
|
15
|
+
end
|
16
|
+
|
17
|
+
def included_in!(desc, val, colxn)
|
18
|
+
raise ArgumentError, "#{desc} must be one of #{colxn.inspect}: got #{val.inspect}", caller unless colxn.include?(val)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'set'
|
2
|
+
#
|
3
|
+
require 'gorillib/object/blank'
|
4
|
+
require 'gorillib/object/try'
|
5
|
+
require 'gorillib/object/try_dup'
|
6
|
+
require 'gorillib/array/extract_options'
|
7
|
+
require 'gorillib/hash/keys'
|
8
|
+
require 'gorillib/hash/slice'
|
9
|
+
require 'gorillib/string/inflector'
|
10
|
+
require 'gorillib/exception/raisers'
|
11
|
+
require 'gorillib/metaprogramming/concern'
|
12
|
+
require 'gorillib/metaprogramming/class_attribute'
|
13
|
+
#
|
14
|
+
require 'gorillib/collection'
|
15
|
+
require 'gorillib/type/extended'
|
16
|
+
require 'gorillib/model/factories'
|
17
|
+
require 'gorillib/model/named_schema'
|
18
|
+
require 'gorillib/model/validate'
|
19
|
+
require 'gorillib/model/errors'
|
20
|
+
#
|
21
|
+
require 'gorillib/model/base'
|
22
|
+
require 'gorillib/model/field'
|
23
|
+
require 'gorillib/model/defaults'
|