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,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'
|