gorillib 0.4.1pre → 0.4.2pre
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 +13 -10
- data/.rspec +1 -1
- data/.yardopts +1 -0
- data/CHANGELOG.md +47 -0
- data/Gemfile +22 -19
- data/Guardfile +23 -9
- data/README.md +12 -12
- data/Rakefile +29 -40
- data/VERSION +1 -1
- data/examples/benchmark/factories_benchmark.rb +87 -0
- data/examples/builder/ironfan.rb +1 -19
- data/examples/hash/slicing_methods.rb +101 -0
- data/gorillib.gemspec +36 -35
- data/lib/gorillib/array/deep_compact.rb +4 -3
- data/lib/gorillib/array/simple_statistics.rb +76 -0
- data/lib/gorillib/base.rb +0 -1
- data/lib/gorillib/builder.rb +15 -30
- data/lib/gorillib/collection.rb +159 -57
- data/lib/gorillib/collection/model_collection.rb +136 -43
- data/lib/gorillib/datetime/parse.rb +4 -2
- data/lib/gorillib/{array → deprecated/array}/average.rb +0 -0
- data/lib/gorillib/{array → deprecated/array}/random.rb +2 -1
- data/lib/gorillib/{array → deprecated/array}/sorted_median.rb +0 -0
- data/lib/gorillib/{array → deprecated/array}/sorted_percentile.rb +0 -0
- data/lib/gorillib/deprecated/array/sorted_sample.rb +13 -0
- data/lib/gorillib/{metaprogramming → deprecated/metaprogramming}/aliasing.rb +0 -0
- data/lib/gorillib/enumerable/sum.rb +3 -3
- data/lib/gorillib/exception/raisers.rb +92 -22
- data/lib/gorillib/factories.rb +550 -0
- data/lib/gorillib/hash/mash.rb +15 -58
- data/lib/gorillib/hashlike/deep_compact.rb +2 -2
- data/lib/gorillib/hashlike/slice.rb +55 -40
- data/lib/gorillib/model.rb +5 -3
- data/lib/gorillib/model/base.rb +33 -119
- data/lib/gorillib/model/defaults.rb +58 -14
- data/lib/gorillib/model/errors.rb +10 -0
- data/lib/gorillib/model/factories.rb +1 -367
- data/lib/gorillib/model/field.rb +40 -18
- data/lib/gorillib/model/fixup.rb +16 -0
- data/lib/gorillib/model/positional_fields.rb +35 -0
- data/lib/gorillib/model/schema_magic.rb +162 -0
- data/lib/gorillib/model/serialization.rb +1 -2
- data/lib/gorillib/model/serialization/csv.rb +59 -0
- data/lib/gorillib/pathname.rb +19 -8
- data/lib/gorillib/some.rb +2 -0
- data/lib/gorillib/string/constantize.rb +17 -10
- data/lib/gorillib/string/inflector.rb +11 -7
- data/lib/gorillib/type/boolean.rb +40 -0
- data/lib/gorillib/type/extended.rb +76 -40
- data/lib/gorillib/type/url.rb +6 -4
- data/lib/gorillib/utils/console.rb +1 -18
- data/lib/gorillib/utils/edge_cases.rb +18 -0
- data/spec/examples/builder/ironfan_spec.rb +5 -10
- data/spec/gorillib/array/compact_blank_spec.rb +36 -21
- data/spec/gorillib/array/simple_statistics_spec.rb +143 -0
- data/spec/gorillib/builder_spec.rb +16 -20
- data/spec/gorillib/collection_spec.rb +131 -35
- data/spec/gorillib/exception/raisers_spec.rb +39 -0
- data/spec/gorillib/hash/deep_compact_spec.rb +3 -3
- data/spec/gorillib/model/{record/defaults_spec.rb → defaults_spec.rb} +5 -1
- data/spec/gorillib/model/factories_spec.rb +335 -0
- data/spec/gorillib/model/{record/overlay_spec.rb → overlay_spec.rb} +0 -0
- data/spec/gorillib/model/serialization_spec.rb +2 -2
- data/spec/gorillib/model_spec.rb +19 -18
- data/spec/gorillib/pathname_spec.rb +7 -7
- data/spec/gorillib/string/truncate_spec.rb +3 -13
- data/spec/gorillib/type/extended_spec.rb +50 -2
- data/spec/gorillib/utils/capture_output_spec.rb +1 -1
- data/spec/spec_helper.rb +10 -7
- data/spec/support/factory_test_helpers.rb +76 -0
- data/spec/support/gorillib_test_helpers.rb +36 -24
- data/spec/support/model_test_helpers.rb +39 -2
- metadata +86 -51
- data/lib/alt/kernel/call_stack.rb +0 -56
- data/lib/gorillib/array/sorted_sample.rb +0 -12
- data/lib/gorillib/builder/field.rb +0 -5
- data/lib/gorillib/collection/has_collection.rb +0 -31
- data/lib/gorillib/collection/list_collection.rb +0 -58
- data/lib/gorillib/exception/confidence.rb +0 -17
- data/lib/gorillib/io/system_helpers.rb +0 -30
- data/lib/gorillib/model/record_schema.rb +0 -9
- data/lib/gorillib/utils/stub_module.rb +0 -33
- 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/gorillib/metaprogramming/aliasing_spec.rb +0 -180
- data/spec/gorillib/model/record/factories_spec.rb +0 -335
- data/spec/support/kcode_test_helper.rb +0 -16
data/lib/gorillib/hash/mash.rb
CHANGED
@@ -6,42 +6,9 @@ class Hash
|
|
6
6
|
#
|
7
7
|
# @return [Mash] This hash as a Mash for string or symbol key access.
|
8
8
|
def to_mash
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
##
|
15
|
-
# Create a hash with *only* key/value pairs in receiver and +allowed+
|
16
|
-
#
|
17
|
-
# { :one => 1, :two => 2, :three => 3 }.only(:one) #=> { :one => 1 }
|
18
|
-
#
|
19
|
-
# @param allowed [Array[String, Symbol]] The hash keys to include.
|
20
|
-
#
|
21
|
-
# @return [Hash] A new hash with only the selected keys.
|
22
|
-
#
|
23
|
-
# @api public
|
24
|
-
def only(*allowed)
|
25
|
-
hash = {}
|
26
|
-
allowed.each {|k| hash[k] = self[k] if self.has_key?(k) }
|
27
|
-
hash
|
28
|
-
end
|
29
|
-
|
30
|
-
##
|
31
|
-
# Create a hash with all key/value pairs in receiver *except* +rejected+
|
32
|
-
#
|
33
|
-
# { :one => 1, :two => 2, :three => 3 }.except(:one)
|
34
|
-
# #=> { :two => 2, :three => 3 }
|
35
|
-
#
|
36
|
-
# @param rejected [Array[String, Symbol]] The hash keys to exclude.
|
37
|
-
#
|
38
|
-
# @return [Hash] A new hash without the selected keys.
|
39
|
-
#
|
40
|
-
# @api public
|
41
|
-
def except(*rejected)
|
42
|
-
hash = self.dup
|
43
|
-
rejected.each {|k| hash.delete(k) }
|
44
|
-
hash
|
9
|
+
hsh = Mash.new(self)
|
10
|
+
hsh.default = default
|
11
|
+
hsh
|
45
12
|
end
|
46
13
|
end
|
47
14
|
|
@@ -109,17 +76,17 @@ class Mash < Hash
|
|
109
76
|
super(convert_key(key))
|
110
77
|
end
|
111
78
|
|
112
|
-
# def include? def has_key? def member?
|
113
79
|
alias_method :include?, :key?
|
114
80
|
alias_method :has_key?, :key?
|
115
|
-
alias_method :member?,
|
81
|
+
alias_method :member?, :key?
|
116
82
|
|
117
83
|
# @param <Object> key The key to fetch. This will be run through convert_key.
|
118
|
-
# @param <Array>
|
84
|
+
# @param <Array> args Default value.
|
85
|
+
# @yield Default block
|
119
86
|
#
|
120
87
|
# @return [Object] The value at key or the default value.
|
121
|
-
def fetch(key, *
|
122
|
-
super(convert_key(key), *
|
88
|
+
def fetch(key, *args, &block)
|
89
|
+
super(convert_key(key), *args, &block)
|
123
90
|
end
|
124
91
|
|
125
92
|
# @param indices [Array]
|
@@ -127,7 +94,7 @@ class Mash < Hash
|
|
127
94
|
#
|
128
95
|
# @return [Array] The values at each of the provided keys
|
129
96
|
def values_at(*indices)
|
130
|
-
indices.
|
97
|
+
indices.map{|key| self[convert_key(key)] }
|
131
98
|
end
|
132
99
|
|
133
100
|
# @param hash<Hash> The hash to merge with the mash.
|
@@ -143,17 +110,6 @@ class Mash < Hash
|
|
143
110
|
super(convert_key(key))
|
144
111
|
end
|
145
112
|
|
146
|
-
# @param [Array[(String, Symbol)]] rejected The mash keys to exclude.
|
147
|
-
#
|
148
|
-
# @return [Mash] A new mash without the selected keys.
|
149
|
-
#
|
150
|
-
# @example
|
151
|
-
# { :one => 1, :two => 2, :three => 3 }.except(:one)
|
152
|
-
# #=> { "two" => 2, "three" => 3 }
|
153
|
-
def except(*rejected)
|
154
|
-
super(*rejected.map {|k| convert_key(k)})
|
155
|
-
end
|
156
|
-
|
157
113
|
# Used to provide the same interface as Hash.
|
158
114
|
#
|
159
115
|
# @return [Mash] This mash unchanged.
|
@@ -161,9 +117,9 @@ class Mash < Hash
|
|
161
117
|
|
162
118
|
# @return [Hash] The mash as a Hash with symbolized keys.
|
163
119
|
def symbolize_keys
|
164
|
-
|
165
|
-
each
|
166
|
-
|
120
|
+
hsh = Hash.new(default)
|
121
|
+
each{|key, val| hsh[key.to_sym] = val }
|
122
|
+
hsh
|
167
123
|
end
|
168
124
|
|
169
125
|
# @return [Hash] The mash as a Hash with string keys.
|
@@ -171,7 +127,8 @@ class Mash < Hash
|
|
171
127
|
Hash.new(default).merge(self)
|
172
128
|
end
|
173
129
|
|
174
|
-
|
130
|
+
protected
|
131
|
+
|
175
132
|
# @param key [Object] The key to convert.
|
176
133
|
#
|
177
134
|
# @return [Object]
|
@@ -194,7 +151,7 @@ class Mash < Hash
|
|
194
151
|
if value.class == Hash
|
195
152
|
value.to_mash
|
196
153
|
elsif value.is_a?(Array)
|
197
|
-
value.
|
154
|
+
value.map{|e| convert_value(e) }
|
198
155
|
else
|
199
156
|
value
|
200
157
|
end
|
@@ -7,9 +7,9 @@ module Gorillib
|
|
7
7
|
# deep_compact! removes all keys with 'blank?' values in the hash, in place, recursively
|
8
8
|
#
|
9
9
|
def deep_compact!
|
10
|
-
|
10
|
+
each_pair do |key, val|
|
11
11
|
val.deep_compact! if val.respond_to?(:deep_compact!)
|
12
|
-
|
12
|
+
delete(key) if val.blank?
|
13
13
|
end
|
14
14
|
self
|
15
15
|
end
|
@@ -1,30 +1,31 @@
|
|
1
|
+
require 'set'
|
1
2
|
module Gorillib
|
2
3
|
module Hashlike
|
3
4
|
module Slice
|
4
|
-
|
5
|
-
#
|
6
|
-
# @return the sliced hash
|
5
|
+
|
6
|
+
# Returns a copy having only the given keys
|
7
7
|
#
|
8
8
|
# @example limit an options hash to valid keys before passing to a method:
|
9
9
|
# def search(criteria = {})
|
10
|
-
# assert_valid_keys(:mass, :velocity, :time)
|
10
|
+
# assert_valid_keys(:mass, :velocity, :time) # gorillib/hash/keys
|
11
11
|
# end
|
12
12
|
# search(options.slice(:mass, :velocity, :time))
|
13
13
|
#
|
14
|
-
# If you have an array of keys you want to limit to,
|
14
|
+
# If you have an array of keys you want to limit to, splat them:
|
15
15
|
#
|
16
16
|
# valid_keys = [:mass, :velocity, :time]
|
17
17
|
# search(options.slice(*valid_keys))
|
18
|
-
|
19
|
-
|
18
|
+
#
|
19
|
+
# @return key/value pairs for keys in self and allowed
|
20
|
+
def slice(*allowed)
|
21
|
+
allowed.map!{|key| convert_key(key) } if respond_to?(:convert_key, true)
|
20
22
|
hash = self.class.new
|
21
|
-
|
23
|
+
allowed.each{|key| hash[key] = self[key] if has_key?(key) }
|
22
24
|
hash
|
23
|
-
end
|
25
|
+
end
|
24
26
|
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# @return a hash containing the removed key/value pairs
|
27
|
+
# Retains only the given keys.
|
28
|
+
# Returns a copy containing the removed key/value pairs.
|
28
29
|
#
|
29
30
|
# @example
|
30
31
|
# hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
|
@@ -32,17 +33,17 @@ module Gorillib
|
|
32
33
|
# # => {:c => 3, :d =>4}
|
33
34
|
# hsh
|
34
35
|
# # => {:a => 1, :b => 2}
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
#
|
37
|
+
# @return the removed key/value pairs
|
38
|
+
def slice!(*allowed)
|
39
|
+
allowed.map!{|key| convert_key(key) } if respond_to?(:convert_key, true)
|
40
|
+
omit = slice(*self.keys - allowed)
|
41
|
+
hash = slice(*allowed)
|
39
42
|
replace(hash)
|
40
43
|
omit
|
41
|
-
end
|
44
|
+
end
|
42
45
|
|
43
|
-
# Removes the
|
44
|
-
#
|
45
|
-
# @return a hash containing the removed key/value pairs
|
46
|
+
# Removes and returns the key/value pairs matching the given keys.
|
46
47
|
#
|
47
48
|
# @example
|
48
49
|
# hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
|
@@ -50,50 +51,64 @@ module Gorillib
|
|
50
51
|
# # => {:a => 1, :b => 2}
|
51
52
|
# hsh
|
52
53
|
# # => {:c => 3, :d =>4}
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
#
|
55
|
+
# @return a copy containing the removed key/value pairs
|
56
|
+
def extract!(*allowed)
|
57
|
+
slice!(* self.keys-allowed)
|
58
|
+
end
|
56
59
|
|
57
|
-
|
60
|
+
# end
|
61
|
+
# module ExceptOnly
|
62
|
+
|
63
|
+
# Return a copy that excludes the given keys
|
58
64
|
#
|
59
65
|
# @example Exclude a blacklist of parameters
|
60
66
|
# @person.update_attributes(params[:person].except(:admin))
|
61
67
|
#
|
62
|
-
# If the receiver responds to +convert_key+, the method is called on each
|
63
|
-
# arguments. This allows +except+ to play nice with hashes with
|
64
|
-
# for instance:
|
68
|
+
# If the receiver responds to +convert_key+, the method is called on each
|
69
|
+
# of the arguments. This allows +except+ to play nice with hashes with
|
70
|
+
# indifferent access for instance:
|
65
71
|
#
|
66
72
|
# @example mash, hash, it does the right thing:
|
67
73
|
# {:a => 1}.to_mash.except(:a) # => {}
|
68
74
|
# {:a => 1}.to_mash.except('a') # => {}
|
69
75
|
#
|
70
|
-
def except(*
|
71
|
-
dup.except!(*
|
76
|
+
def except(*rejected)
|
77
|
+
dup.except!(*rejected)
|
72
78
|
end
|
73
79
|
|
74
|
-
#
|
75
|
-
|
76
|
-
|
80
|
+
# Modifies the hash to exclude the given keys
|
81
|
+
# @see #except
|
82
|
+
#
|
83
|
+
# @return self
|
84
|
+
def except!(*rejected)
|
85
|
+
rejected.each{|key| delete(key) }
|
77
86
|
self
|
78
87
|
end
|
79
88
|
|
89
|
+
# Return a copy having only the given keys
|
90
|
+
#
|
91
|
+
# @example Limit a set of parameters to everything but a few known toggles:
|
92
|
+
# { :one => 1, :two => 2, :three => 3 }.only(:one) #=> { :one => 1 }
|
93
|
+
#
|
94
|
+
# @param [#include?] allowed keys to include.
|
95
|
+
#
|
96
|
+
# @return [Hash] a copy with only the selected keys.
|
80
97
|
def only(*allowed)
|
81
98
|
dup.only!(*allowed)
|
82
99
|
end
|
83
100
|
|
84
|
-
#
|
101
|
+
# Retain only the given keys; return self
|
85
102
|
#
|
86
103
|
# @example Limit a set of parameters to everything but a few known toggles:
|
87
|
-
# { :one => 1, :two => 2, :three => 3 }.only(:one) #=> { :one => 1 }
|
88
|
-
#
|
89
|
-
# @param [Array[String, Symbol]] allowed The hash keys to include.
|
104
|
+
# { :one => 1, :two => 2, :three => 3 }.only!(:one) #=> { :one => 1 }
|
90
105
|
#
|
91
|
-
# @
|
106
|
+
# @param [#include?] allowed keys to include.
|
92
107
|
#
|
93
|
-
# @
|
108
|
+
# @return self
|
94
109
|
def only!(*allowed)
|
95
|
-
allowed
|
96
|
-
|
110
|
+
allowed.map!{|key| convert_key(key) } if respond_to?(:convert_key, true)
|
111
|
+
keep_if{|key, val| allowed.include?(key) }
|
97
112
|
end
|
98
113
|
|
99
114
|
end
|
data/lib/gorillib/model.rb
CHANGED
@@ -11,13 +11,15 @@ require 'gorillib/exception/raisers'
|
|
11
11
|
require 'gorillib/metaprogramming/concern'
|
12
12
|
require 'gorillib/metaprogramming/class_attribute'
|
13
13
|
#
|
14
|
-
require 'gorillib/
|
15
|
-
require 'gorillib/type/extended'
|
16
|
-
require 'gorillib/model/factories'
|
14
|
+
require 'gorillib/factories'
|
15
|
+
# require 'gorillib/type/extended'
|
17
16
|
require 'gorillib/model/named_schema'
|
18
17
|
require 'gorillib/model/validate'
|
19
18
|
require 'gorillib/model/errors'
|
20
19
|
#
|
21
20
|
require 'gorillib/model/base'
|
21
|
+
require 'gorillib/model/schema_magic'
|
22
22
|
require 'gorillib/model/field'
|
23
23
|
require 'gorillib/model/defaults'
|
24
|
+
#
|
25
|
+
require 'gorillib/collection'
|
data/lib/gorillib/model/base.rb
CHANGED
@@ -19,19 +19,14 @@ module Gorillib
|
|
19
19
|
extend Gorillib::Concern
|
20
20
|
|
21
21
|
def initialize(*args, &block)
|
22
|
-
attrs = args
|
23
|
-
if args.present?
|
24
|
-
fns = self.class.field_names
|
25
|
-
ArgumentError.check_arity!(args, 0..fns.length)
|
26
|
-
attrs = attrs.merge(Hash[ fns[0..(args.length-1)].zip(args) ])
|
27
|
-
end
|
22
|
+
attrs = self.class.attrs_hash_from_args(args)
|
28
23
|
receive!(attrs, &block)
|
29
24
|
end
|
30
25
|
|
31
26
|
# Returns a Hash of all attributes
|
32
27
|
#
|
33
28
|
# @example Get attributes
|
34
|
-
# person.attributes # => { :name => "
|
29
|
+
# person.attributes # => { :name => "Emmet Brown", :title => "Dr" }
|
35
30
|
#
|
36
31
|
# @return [{Symbol => Object}] The Hash of all attributes
|
37
32
|
def attributes
|
@@ -69,12 +64,12 @@ module Gorillib
|
|
69
64
|
# or some such. Use `#update_attributes` if your data is already type safe.
|
70
65
|
#
|
71
66
|
# @param [{Symbol => Object}] hsh The values to receive
|
72
|
-
# @return [
|
67
|
+
# @return [nil] nothing
|
73
68
|
def receive!(hsh={})
|
74
69
|
if hsh.respond_to?(:attributes)
|
75
|
-
hsh = hsh.
|
70
|
+
hsh = hsh.compact_attributes
|
76
71
|
else
|
77
|
-
Gorillib::Model::Validate.hashlike!(hsh){ "attributes
|
72
|
+
Gorillib::Model::Validate.hashlike!(hsh){ "attributes for #{self.inspect}" }
|
78
73
|
hsh = hsh.dup
|
79
74
|
end
|
80
75
|
self.class.field_names.each do |field_name|
|
@@ -84,12 +79,12 @@ module Gorillib
|
|
84
79
|
self.send("receive_#{field_name}", val)
|
85
80
|
end
|
86
81
|
handle_extra_attributes(hsh)
|
87
|
-
|
82
|
+
nil
|
88
83
|
end
|
89
84
|
|
90
85
|
def handle_extra_attributes(attrs)
|
91
|
-
@
|
92
|
-
@
|
86
|
+
@_extra_attributes ||= Hash.new
|
87
|
+
@_extra_attributes.merge!(attrs)
|
93
88
|
end
|
94
89
|
|
95
90
|
#
|
@@ -102,7 +97,7 @@ module Gorillib
|
|
102
97
|
# @return [Gorillib::Model] the object itself
|
103
98
|
def update_attributes(hsh)
|
104
99
|
if hsh.respond_to?(:attributes) then hsh = hsh.attributes ; end
|
105
|
-
Gorillib::Model::Validate.hashlike!(hsh){ "attributes
|
100
|
+
Gorillib::Model::Validate.hashlike!(hsh){ "attributes for #{self.inspect}" }
|
106
101
|
self.class.field_names.each do |field_name|
|
107
102
|
if hsh.has_key?(field_name) then val = hsh[field_name]
|
108
103
|
elsif hsh.has_key?(field_name.to_s) then val = hsh[field_name.to_s]
|
@@ -193,24 +188,27 @@ module Gorillib
|
|
193
188
|
attributes == other.attributes
|
194
189
|
end
|
195
190
|
|
196
|
-
# override
|
191
|
+
# override to_inspectable (not this) in your descendant class
|
197
192
|
# @return [String] Human-readable presentation of the attributes
|
198
|
-
def inspect
|
199
|
-
|
193
|
+
def inspect
|
194
|
+
str = '#<' << self.class.name.to_s
|
195
|
+
attrs = to_inspectable
|
196
|
+
if attrs.present?
|
197
|
+
str << '(' << attrs.map{|attr, val| "#{attr}=#{val.respond_to?(:inspect_compact) ? val.inspect_compact : val.inspect}" }.join(", ") << ')'
|
198
|
+
end
|
199
|
+
str << '>'
|
200
|
+
end
|
201
|
+
|
202
|
+
def inspect_compact
|
203
|
+
str = "#<#{self.class.name.to_s}>"
|
200
204
|
end
|
201
205
|
|
202
206
|
# assembles just the given attributes into the inspect string.
|
203
207
|
# @return [String] Human-readable presentation of the attributes
|
204
|
-
def
|
205
|
-
|
206
|
-
if detailed && attrs.present?
|
207
|
-
str << " " << attrs.map do |attr, val|
|
208
|
-
"#{attr}=#{val.is_a?(Gorillib::Model) || val.is_a?(Gorillib::GenericCollection) ? val.inspect(false) : val.inspect}"
|
209
|
-
end.join(", ")
|
210
|
-
end
|
211
|
-
str << ">"
|
208
|
+
def to_inspectable
|
209
|
+
compact_attributes
|
212
210
|
end
|
213
|
-
private :
|
211
|
+
private :to_inspectable
|
214
212
|
|
215
213
|
protected
|
216
214
|
|
@@ -229,7 +227,7 @@ module Gorillib
|
|
229
227
|
# @return [Gorillib::Model] the new object
|
230
228
|
def receive(attrs={}, &block)
|
231
229
|
return nil if attrs.nil?
|
232
|
-
return attrs if
|
230
|
+
return attrs if native?(attrs)
|
233
231
|
#
|
234
232
|
Gorillib::Model::Validate.hashlike!(attrs){ "attributes for #{self.inspect}" }
|
235
233
|
klass = attrs.has_key?(:_type) ? Gorillib::Factory(attrs[:_type]) : self
|
@@ -238,45 +236,13 @@ module Gorillib
|
|
238
236
|
klass.new(attrs, &block)
|
239
237
|
end
|
240
238
|
|
241
|
-
#
|
242
|
-
#
|
243
|
-
# For each field that is defined, a getter and setter will be added as
|
244
|
-
# an instance method to the model. An Field instance will be added to
|
245
|
-
# result of the fields class method.
|
239
|
+
# A `native` object does not need any transformation; it is accepted directly.
|
240
|
+
# By default, an object is native if it `is_a?` this class
|
246
241
|
#
|
247
|
-
# @
|
248
|
-
#
|
249
|
-
|
250
|
-
|
251
|
-
# @param [Class] type The field's type (required)
|
252
|
-
# @option options [String] doc Documentation string for the field (optional)
|
253
|
-
# @option options [Proc, Object] default Default value, or proc that instance can evaluate to find default value
|
254
|
-
#
|
255
|
-
# @return Gorillib::Model::Field
|
256
|
-
def field(field_name, type, options={})
|
257
|
-
options = options.symbolize_keys
|
258
|
-
field_type = options.delete(:field_type){ ::Gorillib::Model::Field }
|
259
|
-
fld = field_type.new(field_name, type, self, options)
|
260
|
-
@_own_fields[fld.name] = fld
|
261
|
-
_reset_descendant_fields
|
262
|
-
fld.send(:inscribe_methods, self)
|
263
|
-
fld
|
264
|
-
end
|
265
|
-
|
266
|
-
# @return [{Symbol => Gorillib::Model::Field}]
|
267
|
-
def fields
|
268
|
-
return @_fields if defined?(@_fields)
|
269
|
-
@_fields = ancestors.reverse.inject({}){|acc, ancestor| acc.merge!(ancestor.try(:_own_fields) || {}) }
|
270
|
-
end
|
271
|
-
|
272
|
-
# @return [true, false] true if the field is defined on this class
|
273
|
-
def has_field?(field_name)
|
274
|
-
fields.has_key?(field_name)
|
275
|
-
end
|
276
|
-
|
277
|
-
# @return [Array<Symbol>] The attribute names
|
278
|
-
def field_names
|
279
|
-
@_field_names ||= fields.keys
|
242
|
+
# @param obj [Object] the object that will be received
|
243
|
+
# @return [true, false] true if the item does not need conversion
|
244
|
+
def native?(obj)
|
245
|
+
obj.is_a?(self)
|
280
246
|
end
|
281
247
|
|
282
248
|
# @return Class name and its attributes
|
@@ -284,62 +250,10 @@ module Gorillib
|
|
284
250
|
# @example Inspect the model's definition.
|
285
251
|
# Person.inspect #=> Person[first_name, last_name]
|
286
252
|
def inspect
|
287
|
-
"#{self.name || 'anon'}[#{ field_names.join(",
|
288
|
-
end
|
289
|
-
|
290
|
-
protected
|
291
|
-
|
292
|
-
attr_reader :_own_fields
|
293
|
-
|
294
|
-
# Ensure that classes inherit all their parents' fields, even if fields
|
295
|
-
# are added after the child class is defined.
|
296
|
-
def _reset_descendant_fields
|
297
|
-
ObjectSpace.each_object(::Class) do |klass|
|
298
|
-
klass.__send__(:remove_instance_variable, '@_fields') if (klass <= self) && klass.instance_variable_defined?('@_fields')
|
299
|
-
klass.__send__(:remove_instance_variable, '@_field_names') if (klass <= self) && klass.instance_variable_defined?('@_field_names')
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
# define the reader method `#foo` for a field named `:foo`
|
304
|
-
def define_attribute_reader(field_name, field_type, visibility)
|
305
|
-
define_meta_module_method(field_name, visibility) do
|
306
|
-
begin
|
307
|
-
read_attribute(field_name)
|
308
|
-
rescue StandardError => err ; err.polish("#{self.class}.#{field_name}") rescue nil ; raise ; end
|
309
|
-
end
|
253
|
+
"#{self.name || 'anon'}[#{ field_names.join(",") }]"
|
310
254
|
end
|
255
|
+
def inspect_compact() self.name || inspect ; end
|
311
256
|
|
312
|
-
# define the writer method `#foo=` for a field named `:foo`
|
313
|
-
def define_attribute_writer(field_name, field_type, visibility)
|
314
|
-
define_meta_module_method("#{field_name}=", visibility) do |val|
|
315
|
-
write_attribute(field_name, val)
|
316
|
-
end
|
317
|
-
end
|
318
|
-
|
319
|
-
# define the present method `#foo?` for a field named `:foo`
|
320
|
-
def define_attribute_tester(field_name, field_type, visibility)
|
321
|
-
field = fields[field_name]
|
322
|
-
define_meta_module_method("#{field_name}?", visibility) do
|
323
|
-
attribute_set?(field_name) || field.has_default?
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
def define_attribute_receiver(field_name, field_type, visibility)
|
328
|
-
define_meta_module_method("receive_#{field_name}", visibility) do |val|
|
329
|
-
begin
|
330
|
-
val = field_type.receive(val)
|
331
|
-
write_attribute(field_name, val)
|
332
|
-
self
|
333
|
-
rescue StandardError => err ; err.polish("#{self.class}.#{field_name} type #{type} on #{val}") rescue nil ; raise ; end
|
334
|
-
end
|
335
|
-
end
|
336
|
-
|
337
|
-
def inherited(base)
|
338
|
-
base.instance_eval do
|
339
|
-
@_own_fields ||= {}
|
340
|
-
end
|
341
|
-
super
|
342
|
-
end
|
343
257
|
end
|
344
258
|
|
345
259
|
self.included do |base|
|