gorillib 0.1.11 → 0.4.0pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. data/.gitignore +1 -0
  2. data/.rspec +1 -2
  3. data/.yardopts +9 -0
  4. data/{CHANGELOG.textile → CHANGELOG.md} +35 -9
  5. data/Gemfile +21 -14
  6. data/Guardfile +19 -0
  7. data/{LICENSE.textile → LICENSE.md} +43 -29
  8. data/README.md +47 -52
  9. data/Rakefile +31 -30
  10. data/TODO.md +32 -0
  11. data/VERSION +1 -1
  12. data/examples/builder/ironfan.rb +133 -0
  13. data/examples/model/simple.rb +17 -0
  14. data/gorillib.gemspec +106 -86
  15. data/lib/alt/kernel/call_stack.rb +56 -0
  16. data/lib/gorillib/array/wrap.rb +53 -0
  17. data/lib/gorillib/base.rb +3 -3
  18. data/lib/gorillib/builder/field.rb +5 -0
  19. data/lib/gorillib/builder.rb +260 -0
  20. data/lib/gorillib/collection/has_collection.rb +31 -0
  21. data/lib/gorillib/collection.rb +129 -0
  22. data/lib/gorillib/configurable.rb +28 -0
  23. data/lib/gorillib/datetime/{flat.rb → to_flat.rb} +0 -0
  24. data/lib/gorillib/exception/confidence.rb +17 -0
  25. data/lib/gorillib/exception/raisers.rb +78 -0
  26. data/lib/gorillib/hash/mash.rb +202 -0
  27. data/lib/gorillib/hashlike/slice.rb +53 -19
  28. data/lib/gorillib/hashlike.rb +5 -3
  29. data/lib/gorillib/io/system_helpers.rb +30 -0
  30. data/lib/gorillib/logger/log.rb +18 -0
  31. data/lib/gorillib/metaprogramming/concern.rb +124 -0
  32. data/lib/gorillib/model/active_model_conversion.rb +68 -0
  33. data/lib/gorillib/model/active_model_naming.rb +87 -0
  34. data/lib/gorillib/model/active_model_shim.rb +33 -0
  35. data/lib/gorillib/model/base.rb +341 -0
  36. data/lib/gorillib/model/defaults.rb +71 -0
  37. data/lib/gorillib/model/errors.rb +14 -0
  38. data/lib/gorillib/model/factories.rb +372 -0
  39. data/lib/gorillib/model/field.rb +146 -0
  40. data/lib/gorillib/model/named_schema.rb +53 -0
  41. data/lib/gorillib/{struct/hashlike_iteration.rb → model/overlay.rb} +0 -0
  42. data/lib/gorillib/model/record_schema.rb +9 -0
  43. data/lib/gorillib/model/serialization.rb +23 -0
  44. data/lib/gorillib/model/validate.rb +22 -0
  45. data/lib/gorillib/model.rb +23 -0
  46. data/lib/gorillib/pathname.rb +78 -0
  47. data/lib/gorillib/{serialization.rb → serialization/to_wire.rb} +0 -0
  48. data/lib/gorillib/some.rb +11 -9
  49. data/lib/gorillib/string/constantize.rb +21 -14
  50. data/lib/gorillib/string/inflections.rb +6 -76
  51. data/lib/gorillib/string/inflector.rb +192 -0
  52. data/lib/gorillib/string/simple_inflector.rb +267 -0
  53. data/lib/gorillib/type/extended.rb +52 -0
  54. data/lib/gorillib/utils/capture_output.rb +28 -0
  55. data/lib/gorillib/utils/console.rb +131 -0
  56. data/lib/gorillib/utils/nuke_constants.rb +9 -0
  57. data/lib/gorillib/utils/stub_module.rb +33 -0
  58. data/spec/examples/builder/ironfan_spec.rb +37 -0
  59. data/spec/extlib/hash_spec.rb +64 -0
  60. data/spec/extlib/mash_spec.rb +312 -0
  61. data/spec/{array → gorillib/array}/compact_blank_spec.rb +2 -2
  62. data/spec/{array → gorillib/array}/extract_options_spec.rb +2 -2
  63. data/spec/gorillib/builder_spec.rb +187 -0
  64. data/spec/gorillib/collection_spec.rb +20 -0
  65. data/spec/gorillib/configurable_spec.rb +62 -0
  66. data/spec/{datetime → gorillib/datetime}/parse_spec.rb +3 -3
  67. data/spec/{datetime/flat_spec.rb → gorillib/datetime/to_flat_spec.rb} +4 -4
  68. data/spec/{enumerable → gorillib/enumerable}/sum_spec.rb +5 -5
  69. data/spec/gorillib/exception/raisers_spec.rb +60 -0
  70. data/spec/{hash → gorillib/hash}/compact_spec.rb +2 -2
  71. data/spec/{hash → gorillib/hash}/deep_compact_spec.rb +3 -3
  72. data/spec/{hash → gorillib/hash}/deep_merge_spec.rb +2 -2
  73. data/spec/{hash → gorillib/hash}/keys_spec.rb +2 -2
  74. data/spec/{hash → gorillib/hash}/reverse_merge_spec.rb +2 -2
  75. data/spec/{hash → gorillib/hash}/slice_spec.rb +2 -2
  76. data/spec/{hash → gorillib/hash}/zip_spec.rb +2 -2
  77. data/spec/{hashlike → gorillib/hashlike}/behave_same_as_hash_spec.rb +6 -3
  78. data/spec/{hashlike → gorillib/hashlike}/deep_hash_spec.rb +2 -2
  79. data/spec/{hashlike → gorillib/hashlike}/hashlike_behavior_spec.rb +32 -30
  80. data/spec/{hashlike → gorillib/hashlike}/hashlike_via_accessors_spec.rb +3 -3
  81. data/spec/{hashlike_spec.rb → gorillib/hashlike_spec.rb} +3 -3
  82. data/spec/{logger → gorillib/logger}/log_spec.rb +2 -2
  83. data/spec/{metaprogramming → gorillib/metaprogramming}/aliasing_spec.rb +3 -3
  84. data/spec/{metaprogramming → gorillib/metaprogramming}/class_attribute_spec.rb +3 -3
  85. data/spec/{metaprogramming → gorillib/metaprogramming}/delegation_spec.rb +3 -3
  86. data/spec/{metaprogramming → gorillib/metaprogramming}/singleton_class_spec.rb +3 -3
  87. data/spec/gorillib/model/record/defaults_spec.rb +108 -0
  88. data/spec/gorillib/model/record/factories_spec.rb +321 -0
  89. data/spec/gorillib/model/record/overlay_spec.rb +46 -0
  90. data/spec/gorillib/model/serialization_spec.rb +48 -0
  91. data/spec/gorillib/model_spec.rb +281 -0
  92. data/spec/{numeric → gorillib/numeric}/clamp_spec.rb +2 -2
  93. data/spec/{object → gorillib/object}/blank_spec.rb +2 -2
  94. data/spec/{object → gorillib/object}/try_dup_spec.rb +2 -2
  95. data/spec/{object → gorillib/object}/try_spec.rb +3 -2
  96. data/spec/gorillib/pathname_spec.rb +114 -0
  97. data/spec/{string → gorillib/string}/constantize_spec.rb +2 -2
  98. data/spec/{string → gorillib/string}/human_spec.rb +2 -2
  99. data/spec/{string → gorillib/string}/inflections_spec.rb +4 -3
  100. data/spec/{string → gorillib/string}/inflector_test_cases.rb +0 -0
  101. data/spec/{string → gorillib/string}/truncate_spec.rb +4 -10
  102. data/spec/gorillib/type/extended_spec.rb +120 -0
  103. data/spec/gorillib/utils/capture_output_spec.rb +71 -0
  104. data/spec/spec_helper.rb +8 -11
  105. data/spec/support/gorillib_test_helpers.rb +66 -0
  106. data/spec/support/hashlike_fuzzing_helper.rb +31 -33
  107. data/spec/support/hashlike_helper.rb +3 -3
  108. data/spec/support/model_test_helpers.rb +81 -0
  109. data/spec/support/shared_examples/included_module.rb +20 -0
  110. metadata +177 -158
  111. data/lib/gorillib/array/average.rb +0 -13
  112. data/lib/gorillib/array/sorted_median.rb +0 -11
  113. data/lib/gorillib/array/sorted_percentile.rb +0 -11
  114. data/lib/gorillib/array/sorted_sample.rb +0 -12
  115. data/lib/gorillib/dsl_object.rb +0 -64
  116. data/lib/gorillib/hash/indifferent_access.rb +0 -207
  117. data/lib/gorillib/hash/tree_merge.rb +0 -4
  118. data/lib/gorillib/hashlike/tree_merge.rb +0 -49
  119. data/lib/gorillib/metaprogramming/cattr_accessor.rb +0 -79
  120. data/lib/gorillib/metaprogramming/mattr_accessor.rb +0 -61
  121. data/lib/gorillib/receiver/active_model_shim.rb +0 -32
  122. data/lib/gorillib/receiver/acts_as_hash.rb +0 -195
  123. data/lib/gorillib/receiver/acts_as_loadable.rb +0 -42
  124. data/lib/gorillib/receiver/locale/en.yml +0 -27
  125. data/lib/gorillib/receiver/tree_diff.rb +0 -74
  126. data/lib/gorillib/receiver/validations.rb +0 -30
  127. data/lib/gorillib/receiver.rb +0 -402
  128. data/lib/gorillib/receiver_model.rb +0 -21
  129. data/lib/gorillib/struct/acts_as_hash.rb +0 -108
  130. data/notes/fancy_hashes_and_receivers.textile +0 -120
  131. data/notes/hash_rdocs.textile +0 -97
  132. data/spec/array/average_spec.rb +0 -24
  133. data/spec/array/sorted_median_spec.rb +0 -18
  134. data/spec/array/sorted_percentile_spec.rb +0 -24
  135. data/spec/array/sorted_sample_spec.rb +0 -28
  136. data/spec/dsl_object_spec.rb +0 -99
  137. data/spec/hash/indifferent_access_spec.rb +0 -391
  138. data/spec/metaprogramming/cattr_accessor_spec.rb +0 -43
  139. data/spec/metaprogramming/mattr_accessor_spec.rb +0 -45
  140. data/spec/receiver/acts_as_hash_spec.rb +0 -295
  141. data/spec/receiver_spec.rb +0 -551
  142. data/spec/struct/acts_as_hash_fuzz_spec.rb +0 -71
  143. 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
@@ -0,0 +1,9 @@
1
+ module Gorillib
2
+ module Model
3
+ class RecordSchema
4
+ field :name, String
5
+ field :doc, String
6
+ field :type, Factory
7
+ end
8
+ end
9
+ end
@@ -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'