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,341 @@
|
|
|
1
|
+
|
|
2
|
+
module Gorillib
|
|
3
|
+
|
|
4
|
+
# Provides a set of class methods for defining a field schema and instance
|
|
5
|
+
# methods for reading and writing attributes.
|
|
6
|
+
#
|
|
7
|
+
# @example Usage
|
|
8
|
+
# class Person
|
|
9
|
+
# include Gorillib::Model
|
|
10
|
+
#
|
|
11
|
+
# field :name, String, :doc => 'Full name of person'
|
|
12
|
+
# field :height, Float, :doc => 'Height in meters'
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# person = Person.new
|
|
16
|
+
# person.name = "Bob Dobbs, Jr"
|
|
17
|
+
# puts person #=> #<Person name="Bob Dobbs, Jr">
|
|
18
|
+
#
|
|
19
|
+
module Model
|
|
20
|
+
extend Gorillib::Concern
|
|
21
|
+
|
|
22
|
+
# Returns a Hash of all attributes
|
|
23
|
+
#
|
|
24
|
+
# @example Get attributes
|
|
25
|
+
# person.attributes # => { :name => "Ben Poweski" }
|
|
26
|
+
#
|
|
27
|
+
# @return [{Symbol => Object}] The Hash of all attributes
|
|
28
|
+
def attributes
|
|
29
|
+
self.class.field_names.inject(Hash.new) do |hsh, fn|
|
|
30
|
+
hsh[fn] = read_attribute(fn)
|
|
31
|
+
hsh
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns a Hash of all attributes *that have been set*
|
|
36
|
+
#
|
|
37
|
+
# @example Get attributes (smurfette is unarmed)
|
|
38
|
+
# smurfette.attributes # => { :name => "Smurfette", :weapon => nil }
|
|
39
|
+
# smurfette.compact_attributes # => { :name => "Smurfette" }
|
|
40
|
+
#
|
|
41
|
+
# @return [{Symbol => Object}] The Hash of all *set* attributes
|
|
42
|
+
def compact_attributes
|
|
43
|
+
self.class.field_names.inject(Hash.new) do |hsh, fn|
|
|
44
|
+
hsh[fn] = read_attribute(fn) if attribute_set?(fn)
|
|
45
|
+
hsh
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
#
|
|
50
|
+
# Accept the given attributes, converting each value to the appropriate
|
|
51
|
+
# type, constructing included models and collections, and other triggers as
|
|
52
|
+
# defined.
|
|
53
|
+
#
|
|
54
|
+
# Use `#receive!` to accept 'dirty' data -- from JSON, from a nested hash,
|
|
55
|
+
# or some such. Use `#update_attributes` if your data is already type safe.
|
|
56
|
+
#
|
|
57
|
+
# @param [{Symbol => Object}] hsh The values to receive
|
|
58
|
+
# @return [Gorillib::Model] the object itself
|
|
59
|
+
def receive!(hsh={})
|
|
60
|
+
if hsh.respond_to?(:attributes) then hsh = hsh.attributes ; end
|
|
61
|
+
Gorillib::Model::Validate.hashlike!("attributes hash for #{self.inspect}", hsh)
|
|
62
|
+
hsh = hsh.symbolize_keys
|
|
63
|
+
self.class.fields.each do |field_name, field|
|
|
64
|
+
next unless hsh.has_key?(field_name)
|
|
65
|
+
self.public_send(:"receive_#{field_name}", hsh[field_name])
|
|
66
|
+
end
|
|
67
|
+
handle_extra_attributes( hsh.reject{|field_name,val| self.class.has_field?(field_name) } )
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def handle_extra_attributes(attrs)
|
|
72
|
+
@extra_attributes ||= Hash.new
|
|
73
|
+
@extra_attributes.merge!(attrs)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#
|
|
77
|
+
# Accept the given attributes, adopting each value directly.
|
|
78
|
+
#
|
|
79
|
+
# Use `#receive!` to accept 'dirty' data -- from JSON, from a nested hash,
|
|
80
|
+
# or some such. Use `#update_attributes` if your data is already type safe.
|
|
81
|
+
#
|
|
82
|
+
# @param [{Symbol => Object}] hsh The values to update with
|
|
83
|
+
# @return [Gorillib::Model] the object itself
|
|
84
|
+
def update_attributes(hsh)
|
|
85
|
+
if hsh.respond_to?(:attributes) then hsh = hsh.attributes ; end
|
|
86
|
+
Gorillib::Model::Validate.hashlike!("attributes hash", hsh)
|
|
87
|
+
self.class.fields.each do |attr, field|
|
|
88
|
+
if hsh.has_key?(attr) then val = hsh[attr]
|
|
89
|
+
elsif hsh.has_key?(attr.to_s) then val = hsh[attr.to_s]
|
|
90
|
+
else next ; end
|
|
91
|
+
write_attribute(attr, val)
|
|
92
|
+
end
|
|
93
|
+
self
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Read a value from the model's attributes.
|
|
97
|
+
#
|
|
98
|
+
# @example Reading an attribute
|
|
99
|
+
# person.read_attribute(:name)
|
|
100
|
+
#
|
|
101
|
+
# @param [String, Symbol, #to_s] field_name Name of the attribute to get.
|
|
102
|
+
#
|
|
103
|
+
# @raise [UnknownAttributeError] if the attribute is unknown
|
|
104
|
+
# @return [Object] The value of the attribute, or nil if it is unset
|
|
105
|
+
def read_attribute(field_name)
|
|
106
|
+
check_field(field_name)
|
|
107
|
+
if instance_variable_defined?("@#{field_name}")
|
|
108
|
+
instance_variable_get("@#{field_name}")
|
|
109
|
+
else
|
|
110
|
+
read_unset_attribute(field_name)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Write the value of a single attribute.
|
|
115
|
+
#
|
|
116
|
+
# @example Writing an attribute
|
|
117
|
+
# person.write_attribute(:name, "Benjamin")
|
|
118
|
+
#
|
|
119
|
+
# @param [String, Symbol, #to_s] field_name Name of the attribute to update.
|
|
120
|
+
# @param [Object] val The value to set for the attribute.
|
|
121
|
+
#
|
|
122
|
+
# @raise [UnknownAttributeError] if the attribute is unknown
|
|
123
|
+
# @return [Object] the attribute's value
|
|
124
|
+
def write_attribute(field_name, val)
|
|
125
|
+
check_field(field_name)
|
|
126
|
+
instance_variable_set("@#{field_name}", val)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Unset an attribute. Subsequent reads of the attribute will return `nil`,
|
|
130
|
+
# and `attribute_set?` for that field will return false.
|
|
131
|
+
#
|
|
132
|
+
# @example Unsetting an attribute
|
|
133
|
+
# obj.write_attribute(:foo, nil)
|
|
134
|
+
# [ obj.read_attribute(:foo), obj.attribute_set?(:foo) ] # => [ nil, true ]
|
|
135
|
+
# person.unset_attribute(:height)
|
|
136
|
+
# [ obj.read_attribute(:foo), obj.attribute_set?(:foo) ] # => [ nil, false ]
|
|
137
|
+
#
|
|
138
|
+
# @param [String, Symbol, #to_s] field_name Name of the attribute to unset.
|
|
139
|
+
#
|
|
140
|
+
# @raise [UnknownAttributeError] if the attribute is unknown
|
|
141
|
+
# @return [Object] the former value if it was set, nil if it was unset
|
|
142
|
+
def unset_attribute(field_name)
|
|
143
|
+
check_field(field_name)
|
|
144
|
+
if instance_variable_defined?("@#{field_name}")
|
|
145
|
+
val = instance_variable_get("@#{field_name}")
|
|
146
|
+
remove_instance_variable("@#{field_name}")
|
|
147
|
+
return val
|
|
148
|
+
else
|
|
149
|
+
return nil
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# True if the attribute is set.
|
|
154
|
+
#
|
|
155
|
+
# Note that an attribute can have the value nil but be set.
|
|
156
|
+
#
|
|
157
|
+
# @param [String, Symbol, #to_s] field_name Name of the attribute to check.
|
|
158
|
+
#
|
|
159
|
+
# @raise [UnknownAttributeError] if the attribute is unknown
|
|
160
|
+
# @return [true, false]
|
|
161
|
+
def attribute_set?(field_name)
|
|
162
|
+
check_field(field_name)
|
|
163
|
+
instance_variable_defined?("@#{field_name}")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Two models are equal if they have the same class and their attributes
|
|
167
|
+
# are equal.
|
|
168
|
+
#
|
|
169
|
+
# @example Compare for equality.
|
|
170
|
+
# model == other
|
|
171
|
+
#
|
|
172
|
+
# @param [Gorillib::Model, Object] other The other model to compare
|
|
173
|
+
#
|
|
174
|
+
# @return [true, false] True if attributes are equal and other is instance of the same Class
|
|
175
|
+
def ==(other)
|
|
176
|
+
return false unless other.instance_of?(self.class)
|
|
177
|
+
attributes == other.attributes
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# override inspect_helper (not this) in your descendant class
|
|
181
|
+
# @return [String] Human-readable presentation of the attributes
|
|
182
|
+
def inspect(detailed=true)
|
|
183
|
+
inspect_helper(detailed, compact_attributes)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# assembles just the given attributes into the inspect string.
|
|
187
|
+
# @return [String] Human-readable presentation of the attributes
|
|
188
|
+
def inspect_helper(detailed, attrs)
|
|
189
|
+
str = "#<" << self.class.name.to_s
|
|
190
|
+
if detailed && attrs.present?
|
|
191
|
+
str << " "
|
|
192
|
+
str << attrs.map do |attr, val|
|
|
193
|
+
"#{attr}=#{val.is_a?(Gorillib::Model) || val.is_a?(Gorillib::Collection) ? val.inspect(false) : val.inspect}"
|
|
194
|
+
end.join(", ")
|
|
195
|
+
end
|
|
196
|
+
str << ">"
|
|
197
|
+
end
|
|
198
|
+
private :inspect_helper
|
|
199
|
+
|
|
200
|
+
protected
|
|
201
|
+
|
|
202
|
+
# @return [true] if the field exists
|
|
203
|
+
# @raise [UnknownFieldError] if the field is missing
|
|
204
|
+
def check_field(field_name)
|
|
205
|
+
return true if self.class.has_field?(field_name)
|
|
206
|
+
raise UnknownFieldError, "unknown field: #{field_name} for #{self}"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
module ClassMethods
|
|
210
|
+
|
|
211
|
+
def typename
|
|
212
|
+
Gorillib::Inflector.underscore(self.name).gsub(%r{/}, '.')
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
#
|
|
216
|
+
# Receive external data, type-converting and creating contained models as necessary
|
|
217
|
+
#
|
|
218
|
+
# @return [Gorillib::Model] the new object
|
|
219
|
+
def receive(attrs={}, &block)
|
|
220
|
+
return nil if attrs.nil?
|
|
221
|
+
return attrs if attrs.is_a?(self)
|
|
222
|
+
Gorillib::Model::Validate.hashlike!("attributes for #{self}", attrs)
|
|
223
|
+
klass = attrs.has_key?(:_type) ? Gorillib::Factory(attrs[:_type]) : self
|
|
224
|
+
warn "factory #{self} doesn't match type specified in #{attrs}" unless klass <= self
|
|
225
|
+
obj = klass.new
|
|
226
|
+
obj.receive!(attrs, &block)
|
|
227
|
+
obj
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Defines a new field
|
|
231
|
+
#
|
|
232
|
+
# For each field that is defined, a getter and setter will be added as
|
|
233
|
+
# an instance method to the model. An Field instance will be added to
|
|
234
|
+
# result of the fields class method.
|
|
235
|
+
#
|
|
236
|
+
# @example
|
|
237
|
+
# field :height, Integer
|
|
238
|
+
#
|
|
239
|
+
# @param [Symbol] field_name The field name. Must start with `[A-Za-z_]` and subsequently contain only `[A-Za-z0-9_]`
|
|
240
|
+
# @param [Class] type The field's type (required)
|
|
241
|
+
# @option options [String] doc Documentation string for the field (optional)
|
|
242
|
+
# @option options [Proc, Object] default Default value, or proc that instance can evaluate to find default value
|
|
243
|
+
#
|
|
244
|
+
# @return Gorillib::Model::Field
|
|
245
|
+
def field(field_name, type, options={})
|
|
246
|
+
options = options.symbolize_keys
|
|
247
|
+
field_type = options.delete(:field_type){ ::Gorillib::Model::Field }
|
|
248
|
+
fld = field_type.new(field_name, type, self, options)
|
|
249
|
+
@_own_fields[fld.name] = fld
|
|
250
|
+
_reset_descendant_fields
|
|
251
|
+
fld.send(:inscribe_methods, self)
|
|
252
|
+
fld
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# @return [{Symbol => Gorillib::Model::Field}]
|
|
256
|
+
def fields
|
|
257
|
+
return @_fields if defined?(@_fields)
|
|
258
|
+
@_fields = ancestors.reverse.inject({}){|acc, ancestor| acc.merge!(ancestor.try(:_own_fields) || {}) }
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# @return [true, false] true if the field is defined on this class
|
|
262
|
+
def has_field?(field_name)
|
|
263
|
+
fields.has_key?(field_name.to_sym)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# @return [Array<Symbol>] The attribute names
|
|
267
|
+
def field_names
|
|
268
|
+
fields.keys
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# @return Class name and its attributes
|
|
272
|
+
#
|
|
273
|
+
# @example Inspect the model's definition.
|
|
274
|
+
# Person.inspect #=> Person[first_name, last_name]
|
|
275
|
+
def inspect
|
|
276
|
+
"#{self.name || 'anon'}[#{ field_names.join(", ") }]"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
protected
|
|
280
|
+
|
|
281
|
+
attr_reader :_own_fields
|
|
282
|
+
|
|
283
|
+
# Ensure that classes inherit all their parents' fields, even if fields
|
|
284
|
+
# are added after the child class is defined.
|
|
285
|
+
def _reset_descendant_fields
|
|
286
|
+
ObjectSpace.each_object(::Class) do |klass|
|
|
287
|
+
klass.__send__(:remove_instance_variable, '@_fields') if klass <= self && klass.instance_variable_defined?('@_fields')
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# define the reader method `#foo` for a field named `:foo`
|
|
292
|
+
def define_attribute_reader(field_name, field_type, visibility)
|
|
293
|
+
define_meta_module_method(field_name, visibility) do
|
|
294
|
+
begin
|
|
295
|
+
read_attribute(field_name)
|
|
296
|
+
rescue StandardError => err ; err.polish("#{self.class}.#{field_name}") rescue nil ; raise ; end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# define the writer method `#foo=` for a field named `:foo`
|
|
301
|
+
def define_attribute_writer(field_name, field_type, visibility)
|
|
302
|
+
define_meta_module_method("#{field_name}=", visibility) do |val|
|
|
303
|
+
write_attribute(field_name, val)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# define the present method `#foo?` for a field named `:foo`
|
|
308
|
+
def define_attribute_tester(field_name, field_type, visibility)
|
|
309
|
+
define_meta_module_method("#{field_name}?", visibility) do
|
|
310
|
+
attribute_set?(field_name)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def define_attribute_receiver(field_name, field_type, visibility)
|
|
315
|
+
define_meta_module_method("receive_#{field_name}", visibility) do |val|
|
|
316
|
+
begin
|
|
317
|
+
val = field_type.receive(val)
|
|
318
|
+
write_attribute(field_name, val)
|
|
319
|
+
self
|
|
320
|
+
rescue StandardError => err ; err.polish("#{self.class}.#{field_name} type #{type} on #{val}") rescue nil ; raise ; end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def inherited(base)
|
|
325
|
+
base.instance_eval do
|
|
326
|
+
@_own_fields ||= {}
|
|
327
|
+
end
|
|
328
|
+
super
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
self.included do |base|
|
|
333
|
+
base.instance_eval do
|
|
334
|
+
extend Gorillib::Model::NamedSchema
|
|
335
|
+
extend Gorillib::Model::ClassMethods
|
|
336
|
+
@_own_fields ||= {}
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
end
|
|
341
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Gorillib
|
|
2
|
+
module Model
|
|
3
|
+
|
|
4
|
+
Field.class_eval do
|
|
5
|
+
field :default, :whatever
|
|
6
|
+
|
|
7
|
+
# @return [true, false] true if the field has a default value/proc set
|
|
8
|
+
def has_default?
|
|
9
|
+
attribute_set?(:default)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# This is called by `read_attribute` if an attribute is unset; you should
|
|
14
|
+
# not call this directly. You might use this to provide defaults, or lazy
|
|
15
|
+
# access, or layered resolution.
|
|
16
|
+
#
|
|
17
|
+
# @param [String, Symbol, #to_s] field_name Name of the attribute to unset.
|
|
18
|
+
# @return [nil] Ze goggles! Zey do nussing!
|
|
19
|
+
def read_unset_attribute(field_name)
|
|
20
|
+
field = self.class.fields[field_name]
|
|
21
|
+
return unless field.has_default?
|
|
22
|
+
write_attribute(field.name, attribute_default(field))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# FieldDefaults allows defaults to be declared for your fields
|
|
26
|
+
#
|
|
27
|
+
# Defaults are declared by passing the :default option to the field
|
|
28
|
+
# class method. If you need the default to be dynamic, pass a lambda, Proc,
|
|
29
|
+
# or any object that responds to #call as the value to the :default option
|
|
30
|
+
# and the result will calculated on initialization. These dynamic defaults
|
|
31
|
+
# can depend on the values of other fields.
|
|
32
|
+
#
|
|
33
|
+
# @example Usage
|
|
34
|
+
# class Person
|
|
35
|
+
# field :first_name, String, :default => "John"
|
|
36
|
+
# field :last_name, String, :default => "Doe"
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# person = Person.new
|
|
40
|
+
# person.first_name #=> "John"
|
|
41
|
+
# person.last_name #=> "Doe"
|
|
42
|
+
#
|
|
43
|
+
# @example Dynamic Default
|
|
44
|
+
# class Event
|
|
45
|
+
# field :start_date, Date
|
|
46
|
+
# field :end_date, Date, :default => ->{ start_date }
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# event = Event.receive(:start_date => "2012-01-01")
|
|
50
|
+
# event.end_date.to_s #=> "2012-01-01"
|
|
51
|
+
#
|
|
52
|
+
|
|
53
|
+
protected
|
|
54
|
+
|
|
55
|
+
# the actual default value to assign to the attribute
|
|
56
|
+
def attribute_default(field)
|
|
57
|
+
return unless field.has_default?
|
|
58
|
+
val = field.default
|
|
59
|
+
case
|
|
60
|
+
when (val.is_a?(Proc) || val.is_a?(UnboundMethod)) && (val.arity == 0)
|
|
61
|
+
self.instance_exec(&val)
|
|
62
|
+
when val.respond_to?(:call)
|
|
63
|
+
val.call(self, field.name)
|
|
64
|
+
else
|
|
65
|
+
val.try_dup
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Gorillib
|
|
2
|
+
module Model
|
|
3
|
+
|
|
4
|
+
# All exceptions defined by Gorillib::Model include this module.
|
|
5
|
+
module Error
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Exception raised if attempting to assign unknown fields
|
|
9
|
+
class UnknownFieldError < ::NoMethodError
|
|
10
|
+
include Gorillib::Model::Error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
end
|