gorillib 0.4.0pre → 0.4.1pre
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/CHANGELOG.md +36 -1
- data/Gemfile +23 -19
- data/Guardfile +1 -1
- data/Rakefile +31 -31
- data/TODO.md +2 -30
- data/VERSION +1 -1
- data/examples/builder/ironfan.rb +4 -4
- data/gorillib.gemspec +40 -25
- data/lib/gorillib/array/average.rb +13 -0
- data/lib/gorillib/array/sorted_median.rb +11 -0
- data/lib/gorillib/array/sorted_percentile.rb +11 -0
- data/lib/gorillib/array/sorted_sample.rb +12 -0
- data/lib/gorillib/builder.rb +8 -14
- data/lib/gorillib/collection/has_collection.rb +31 -31
- data/lib/gorillib/collection/list_collection.rb +58 -0
- data/lib/gorillib/collection/model_collection.rb +63 -0
- data/lib/gorillib/collection.rb +57 -85
- data/lib/gorillib/logger/log.rb +26 -22
- data/lib/gorillib/model/base.rb +52 -39
- data/lib/gorillib/model/doc_string.rb +15 -0
- data/lib/gorillib/model/factories.rb +56 -61
- data/lib/gorillib/model/lint.rb +24 -0
- data/lib/gorillib/model/serialization.rb +12 -2
- data/lib/gorillib/model/validate.rb +2 -2
- data/lib/gorillib/pathname.rb +21 -6
- data/lib/gorillib/some.rb +2 -0
- data/lib/gorillib/type/extended.rb +0 -2
- data/lib/gorillib/type/url.rb +9 -0
- data/lib/gorillib/utils/console.rb +4 -1
- data/notes/HOWTO.md +22 -0
- data/notes/bucket.md +155 -0
- data/notes/builder.md +170 -0
- data/notes/collection.md +81 -0
- data/notes/factories.md +86 -0
- data/notes/model-overlay.md +209 -0
- data/notes/model.md +135 -0
- data/notes/structured-data-classes.md +127 -0
- data/spec/array/average_spec.rb +24 -0
- data/spec/array/sorted_median_spec.rb +18 -0
- data/spec/array/sorted_percentile_spec.rb +24 -0
- data/spec/array/sorted_sample_spec.rb +28 -0
- data/spec/gorillib/builder_spec.rb +46 -28
- data/spec/gorillib/collection_spec.rb +195 -10
- data/spec/gorillib/model/lint_spec.rb +28 -0
- data/spec/gorillib/model/record/factories_spec.rb +27 -13
- data/spec/gorillib/model/serialization_spec.rb +3 -5
- data/spec/gorillib/model_spec.rb +86 -104
- data/spec/spec_helper.rb +2 -1
- data/spec/support/gorillib_test_helpers.rb +83 -7
- data/spec/support/model_test_helpers.rb +9 -28
- metadata +52 -44
- data/lib/gorillib/configurable.rb +0 -28
- data/spec/gorillib/configurable_spec.rb +0 -62
- data/spec/support/shared_examples/included_module.rb +0 -20
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'gorillib/collection'
|
2
|
+
|
3
|
+
module Gorillib
|
4
|
+
class ListCollection < Gorillib::GenericCollection
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@clxn = Array.new
|
8
|
+
end
|
9
|
+
|
10
|
+
# common to all collections, delegable to array
|
11
|
+
delegate :to_a, :each, :to => :clxn
|
12
|
+
|
13
|
+
# Add the new items in-place; given items clobber existing items
|
14
|
+
# @param other [{Symbol => Object}, Array<Object>] a hash of key=>item pairs or a list of items
|
15
|
+
# @return [Gorillib::Collection] the collection
|
16
|
+
def receive!(other)
|
17
|
+
clxn.concat( convert_collection(other) ).uniq!
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Array] an array holding the items
|
22
|
+
def values ; to_a ; end
|
23
|
+
|
24
|
+
# iterate over each value in the collection
|
25
|
+
def each_value(&block); each(&block) ; end
|
26
|
+
|
27
|
+
# # removed on purpose, pending us understanding what the
|
28
|
+
# # even-yet-simpler-still collection should be.
|
29
|
+
#
|
30
|
+
# delegate :[], :[]=, :fetch, :to => :clxn
|
31
|
+
#
|
32
|
+
# # Deletes the entry whose index is `idx`, returning the corresponding
|
33
|
+
# # value. If the index is out of bounds, returns nil. If the optional code
|
34
|
+
# # block is given and the index is out of bounds, pass it the index and
|
35
|
+
# # return the result of block.
|
36
|
+
# #
|
37
|
+
# # @return the now-delete value, if found; otherwise, the result of the
|
38
|
+
# # block, if given; otherwise nil.
|
39
|
+
# def delete(idx, &block)
|
40
|
+
# if idx < length
|
41
|
+
# clxn.delete_at(idx)
|
42
|
+
# elsif block_given?
|
43
|
+
# yield(idx)
|
44
|
+
# else
|
45
|
+
# nil
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
# - if the given collection responds_to `to_hash`, it is received into the internal collection; each hash key *must* match the id of its value or results are undefined.
|
52
|
+
# - otherwise, it uses a hash generated from the id/value pairs of each object in the given collection.
|
53
|
+
def convert_collection(cc)
|
54
|
+
cc.respond_to?(:values) ? cc.values : cc.to_a
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Gorillib
|
2
|
+
class ModelCollection < Gorillib::Collection
|
3
|
+
# [String, Symbol] Method invoked on a new item to generate its collection key; :to_key by default
|
4
|
+
attr_accessor :key_method
|
5
|
+
# The default `key_method` invoked on a new item to generate its collection key
|
6
|
+
DEFAULT_KEY_METHOD = :to_key
|
7
|
+
|
8
|
+
# [Class, #receive] Factory for generating a new collection item.
|
9
|
+
class_attribute :factory, :instance_writer => false
|
10
|
+
singleton_class.class_eval{ protected :factory= }
|
11
|
+
|
12
|
+
def initialize(key_meth=nil, obj_factory=nil)
|
13
|
+
@factory = Gorillib::Factory(obj_factory) if obj_factory
|
14
|
+
@clxn = Hash.new
|
15
|
+
@key_method = key_meth || DEFAULT_KEY_METHOD
|
16
|
+
end
|
17
|
+
|
18
|
+
# Adds an item in-place
|
19
|
+
# @return [Gorillib::Collection] the collection
|
20
|
+
def <<(val)
|
21
|
+
receive! [val]
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def create(*args, &block)
|
26
|
+
item = factory.receive(*args)
|
27
|
+
self << item
|
28
|
+
item
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_or_create(key, *args, &block)
|
32
|
+
if include?(key)
|
33
|
+
obj = fetch(key)
|
34
|
+
obj.receive!(*args, &block)
|
35
|
+
obj
|
36
|
+
else
|
37
|
+
attrs = args.extract_options!.merge(key_method => key)
|
38
|
+
create(*args, attrs, &block)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def convert_value(val)
|
45
|
+
return val unless factory
|
46
|
+
return nil if val.nil?
|
47
|
+
factory.receive(val)
|
48
|
+
end
|
49
|
+
|
50
|
+
# - if the given collection responds_to `to_hash`, it is received into the internal collection; each hash key *must* match the id of its value or results are undefined.
|
51
|
+
# - otherwise, it receives a hash generates from the id/value pairs of each object in the given collection.
|
52
|
+
def convert_collection(cc)
|
53
|
+
return cc.to_hash if cc.respond_to?(:to_hash)
|
54
|
+
cc.inject({}) do |acc, val|
|
55
|
+
val = convert_value(val)
|
56
|
+
key = val.public_send(key_method)
|
57
|
+
acc[key] = val
|
58
|
+
acc
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
data/lib/gorillib/collection.rb
CHANGED
@@ -2,51 +2,36 @@ require 'gorillib/metaprogramming/delegation'
|
|
2
2
|
require 'gorillib/metaprogramming/class_attribute'
|
3
3
|
|
4
4
|
module Gorillib
|
5
|
-
class Collection
|
6
|
-
# [String, Symbol] Method invoked on a new item to generate its collection key; :to_key by default
|
7
|
-
attr_accessor :key_method
|
8
|
-
# The default `key_method` invoked on a new item to generate its collection key
|
9
|
-
DEFAULT_KEY_METHOD = :to_key
|
10
|
-
|
11
|
-
# [Class, #receive] Factory for generating a new collection item.
|
12
|
-
class_attribute :factory, :instance_writer => false
|
13
|
-
singleton_class.class_eval{ protected :factory= }
|
14
5
|
|
6
|
+
#
|
7
|
+
# A generic collection stores objects uniquely, in the order added. It responds to:
|
8
|
+
# - receive!, values, to_a, each and each_value;
|
9
|
+
# - length, size, empty?, blank?
|
10
|
+
#
|
11
|
+
# A Collection additionally lets you store and retrieve things by label:
|
12
|
+
# - [], []=, include?, fetch, delete, each_pair, to_hash.
|
13
|
+
#
|
14
|
+
# A ModelCollection adds:
|
15
|
+
# - `key_method`: called on objects to get their key; `to_key` by default.
|
16
|
+
# - `factory`: generates new objects, converts received objects
|
17
|
+
# - `<<`: adds object under its `key_method` key
|
18
|
+
# - `receive!`s an array by auto-keying the elements, or a hash by trusting what you give it
|
19
|
+
# - `update_or_create: if absent, creates object with given attributes and
|
20
|
+
# `key_method => key`; if present, updates with given attributes.
|
21
|
+
#
|
22
|
+
class GenericCollection
|
15
23
|
# [{Symbol => Object}] The actual store of items, but not for you to mess with
|
16
24
|
attr_reader :clxn
|
17
25
|
protected :clxn
|
18
26
|
|
19
|
-
delegate :[], :[]=, :delete, :fetch, :to => :clxn
|
20
|
-
delegate :keys, :values, :each_pair, :each_value, :to => :clxn
|
21
|
-
delegate :has_key?, :include?, :length, :size, :empty?, :blank?, :to => :clxn
|
22
|
-
|
23
|
-
def initialize(factory=nil, key_method=DEFAULT_KEY_METHOD)
|
24
|
-
@key_method = key_method
|
25
|
-
@factory = factory unless factory.nil?
|
26
|
-
@clxn = {}
|
27
|
-
end
|
28
|
-
|
29
|
-
# @return [Array] an array holding the items
|
30
|
-
def to_a ; values ; end
|
31
|
-
# @return [{Symbol => Object}] a hash of key=>item pairs
|
32
|
-
def to_hash ; clxn.dup ; end
|
33
|
-
|
34
|
-
# Merge the new items in-place; given items clobber existing items
|
35
|
-
# @param other [{Symbol => Object}, Array<Object>] a hash of key=>item pairs or a list of items
|
36
|
-
# @return [Gorillib::Collection] the collection
|
37
|
-
def merge!(other)
|
38
|
-
clxn.merge!( convert_collection(other) )
|
39
|
-
self
|
40
|
-
end
|
41
|
-
alias_method :concat, :merge!
|
42
|
-
alias_method :receive!, :merge!
|
43
|
-
|
44
27
|
def self.receive(items, *args)
|
45
28
|
coll = new(*args)
|
46
29
|
coll.receive!(items)
|
47
30
|
coll
|
48
31
|
end
|
49
32
|
|
33
|
+
delegate :length, :size, :empty?, :blank?, :to => :clxn
|
34
|
+
|
50
35
|
# Two collections are equal if they have the same class and their contents are equal
|
51
36
|
#
|
52
37
|
# @param [Gorillib::Collection, Object] other The other collection to compare
|
@@ -56,74 +41,61 @@ module Gorillib
|
|
56
41
|
clxn == other.send(:clxn)
|
57
42
|
end
|
58
43
|
|
59
|
-
|
60
|
-
# @param other [{Symbol => Object}, Array<Object>] a hash of key=>item pairs or a list of items
|
61
|
-
# @return [Gorillib::Collection] a new merged collection
|
62
|
-
def merge(other)
|
63
|
-
dup.merge!(other)
|
64
|
-
end
|
65
|
-
|
66
|
-
def create(*args)
|
67
|
-
item = factory.receive(*args)
|
68
|
-
self << item
|
69
|
-
item
|
70
|
-
end
|
71
|
-
|
72
|
-
def find_or_create(key)
|
73
|
-
fetch(key){ create(key_method => key) }
|
74
|
-
end
|
75
|
-
|
76
|
-
# Adds an item in-place
|
77
|
-
# @return [Gorillib::Collection] the collection
|
78
|
-
def <<(val)
|
79
|
-
merge! [val]
|
80
|
-
self
|
81
|
-
end
|
44
|
+
public
|
82
45
|
|
83
46
|
# @return [String] string describing the collection's array representation
|
84
47
|
def to_s ; to_a.to_s ; end
|
85
48
|
# @return [String] string describing the collection's array representation
|
86
49
|
def inspect(detailed=true)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
"%-15s %s" % ["#{key}:", val.inspect]
|
91
|
-
end.join(",\n ")
|
92
|
-
else
|
93
|
-
str << keys.join(", ")
|
94
|
-
end
|
95
|
-
str << " }"
|
50
|
+
if detailed then guts = clxn.map{|key, val| "%-15s %s" % ["#{key}:", val.inspect] }.join(",\n ")
|
51
|
+
else guts = keys.join(", ") ; end
|
52
|
+
["c{ ", guts, " }"].join
|
96
53
|
end
|
97
54
|
# @return [Array] serializable array representation of the collection
|
98
|
-
def to_wire(options)
|
55
|
+
def to_wire(options={})
|
99
56
|
to_a.map{|el| el.respond_to?(:to_wire) ? el.to_wire(options) : el }
|
100
57
|
end
|
101
|
-
|
58
|
+
# same as #to_wire
|
59
|
+
def as_json(*args) to_wire(*args) ; end
|
102
60
|
# @return [String] JSON serialization of the collection's array representation
|
103
|
-
def to_json(*args)
|
104
|
-
|
105
|
-
|
61
|
+
def to_json(*args) to_wire(*args).to_json(*args) ; end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
class Collection < Gorillib::GenericCollection
|
66
|
+
def initialize
|
67
|
+
@clxn = Hash.new
|
106
68
|
end
|
107
69
|
|
108
|
-
|
70
|
+
delegate :[], :[]=, :fetch, :delete, :to => :clxn
|
71
|
+
delegate :values, :to => :clxn
|
72
|
+
delegate :keys, :each_pair, :each_value, :to => :clxn
|
73
|
+
delegate :include?, :to => :clxn
|
74
|
+
|
75
|
+
# @return [Array] an array holding the items
|
76
|
+
def to_a ; values ; end
|
77
|
+
# @return [{Symbol => Object}] a hash of key=>item pairs
|
78
|
+
def to_hash ; clxn.dup ; end
|
79
|
+
|
80
|
+
# iterate over each value in the collection
|
81
|
+
def each(&block); each_value(&block) ; end
|
109
82
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
83
|
+
# Add the new items in-place; given items clobber existing items
|
84
|
+
# @param other [{Symbol => Object}, Array<Object>] a hash of key=>item pairs or a list of items
|
85
|
+
# @return [Gorillib::Collection] the collection
|
86
|
+
def receive!(other)
|
87
|
+
clxn.merge!( convert_collection(other) )
|
88
|
+
self
|
114
89
|
end
|
115
90
|
|
116
|
-
|
117
|
-
|
91
|
+
protected
|
92
|
+
|
93
|
+
# - if the given collection responds_to `to_hash`, it is received into the internal collection; each hash key *must* match the id of its value or results are undefined.
|
94
|
+
# - otherwise, it receives a hash generates from the id/value pairs of each object in the given collection.
|
118
95
|
def convert_collection(cc)
|
119
96
|
return cc.to_hash if cc.respond_to?(:to_hash)
|
120
|
-
|
121
|
-
val = convert_value(val)
|
122
|
-
key = val.public_send(key_method).to_sym
|
123
|
-
acc[key] = val
|
124
|
-
acc
|
125
|
-
end
|
97
|
+
raise "a #{self.class} can only receive a hash with explicitly-labelled contents."
|
126
98
|
end
|
127
|
-
end
|
128
99
|
|
100
|
+
end
|
129
101
|
end
|
data/lib/gorillib/logger/log.rb
CHANGED
@@ -1,29 +1,12 @@
|
|
1
|
-
require 'logger'
|
2
|
-
|
3
1
|
#
|
4
2
|
# A convenient logger.
|
5
3
|
#
|
6
|
-
#
|
4
|
+
# to override its creation, simply define the top-level constant `::Log`
|
7
5
|
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# Log = Log4r::Logger.new('wukong')
|
13
|
-
# Log.outputters = Log4r::Outputter.stderr
|
14
|
-
# # require 'logger'
|
15
|
-
# # Log = Logger.new(STDERR)
|
16
|
-
# end
|
17
|
-
|
18
|
-
# require 'log_buddy'; LogBuddy.init :log_to_stdout => false, :logger => Log
|
19
|
-
# LogBuddy::Utils.module_eval do
|
20
|
-
# def arg_and_blk_debug(arg, blk)
|
21
|
-
# result = eval(arg, blk.binding)
|
22
|
-
# result_str = obj_to_string(result, :quote_strings => true)
|
23
|
-
# LogBuddy.debug(%[#{arg} = #{result_str}])
|
24
|
-
# end
|
25
|
-
# end
|
26
|
-
|
6
|
+
unless defined?(::Log)
|
7
|
+
require 'logger'
|
8
|
+
::Log = Logger.new($stderr)
|
9
|
+
end
|
27
10
|
|
28
11
|
def Log.dump *args
|
29
12
|
self.debug([
|
@@ -31,3 +14,24 @@ def Log.dump *args
|
|
31
14
|
caller.first
|
32
15
|
].join("\t"))
|
33
16
|
end unless Log.respond_to?(:dump)
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
# TODO: allow swappable loggers more cleanly
|
21
|
+
|
22
|
+
# unless defined?(Log)
|
23
|
+
# require 'log4r'
|
24
|
+
# Log = Log4r::Logger.new('wukong')
|
25
|
+
# Log.outputters = Log4r::Outputter.stderr
|
26
|
+
# # require 'logger'
|
27
|
+
# # Log = Logger.new(STDERR)
|
28
|
+
# end
|
29
|
+
|
30
|
+
# require 'log_buddy'; LogBuddy.init :log_to_stdout => false, :logger => Log
|
31
|
+
# LogBuddy::Utils.module_eval do
|
32
|
+
# def arg_and_blk_debug(arg, blk)
|
33
|
+
# result = eval(arg, blk.binding)
|
34
|
+
# result_str = obj_to_string(result, :quote_strings => true)
|
35
|
+
# LogBuddy.debug(%[#{arg} = #{result_str}])
|
36
|
+
# end
|
37
|
+
# end
|
data/lib/gorillib/model/base.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
module Gorillib
|
3
2
|
|
4
3
|
# Provides a set of class methods for defining a field schema and instance
|
@@ -19,6 +18,16 @@ module Gorillib
|
|
19
18
|
module Model
|
20
19
|
extend Gorillib::Concern
|
21
20
|
|
21
|
+
def initialize(*args, &block)
|
22
|
+
attrs = args.extract_options!
|
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
|
28
|
+
receive!(attrs, &block)
|
29
|
+
end
|
30
|
+
|
22
31
|
# Returns a Hash of all attributes
|
23
32
|
#
|
24
33
|
# @example Get attributes
|
@@ -32,6 +41,11 @@ module Gorillib
|
|
32
41
|
end
|
33
42
|
end
|
34
43
|
|
44
|
+
# @return [Array[Object]] all the attributes, in field order, with `nil` where unset
|
45
|
+
def attribute_values
|
46
|
+
self.class.field_names.map{|fn| read_attribute(fn) }
|
47
|
+
end
|
48
|
+
|
35
49
|
# Returns a Hash of all attributes *that have been set*
|
36
50
|
#
|
37
51
|
# @example Get attributes (smurfette is unarmed)
|
@@ -57,14 +71,19 @@ module Gorillib
|
|
57
71
|
# @param [{Symbol => Object}] hsh The values to receive
|
58
72
|
# @return [Gorillib::Model] the object itself
|
59
73
|
def receive!(hsh={})
|
60
|
-
if hsh.respond_to?(:attributes)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
self.public_send(:"receive_#{field_name}", hsh[field_name])
|
74
|
+
if hsh.respond_to?(:attributes)
|
75
|
+
hsh = hsh.attributes
|
76
|
+
else
|
77
|
+
Gorillib::Model::Validate.hashlike!(hsh){ "attributes hash for #{self.inspect}" }
|
78
|
+
hsh = hsh.dup
|
66
79
|
end
|
67
|
-
|
80
|
+
self.class.field_names.each do |field_name|
|
81
|
+
if hsh.has_key?(field_name) then val = hsh.delete(field_name)
|
82
|
+
elsif hsh.has_key?(field_name.to_s) then val = hsh.delete(field_name.to_s)
|
83
|
+
else next ; end
|
84
|
+
self.send("receive_#{field_name}", val)
|
85
|
+
end
|
86
|
+
handle_extra_attributes(hsh)
|
68
87
|
self
|
69
88
|
end
|
70
89
|
|
@@ -83,12 +102,12 @@ module Gorillib
|
|
83
102
|
# @return [Gorillib::Model] the object itself
|
84
103
|
def update_attributes(hsh)
|
85
104
|
if hsh.respond_to?(:attributes) then hsh = hsh.attributes ; end
|
86
|
-
Gorillib::Model::Validate.hashlike!("attributes hash"
|
87
|
-
self.class.
|
88
|
-
if hsh.has_key?(
|
89
|
-
elsif hsh.has_key?(
|
105
|
+
Gorillib::Model::Validate.hashlike!(hsh){ "attributes hash for #{self.inspect}" }
|
106
|
+
self.class.field_names.each do |field_name|
|
107
|
+
if hsh.has_key?(field_name) then val = hsh[field_name]
|
108
|
+
elsif hsh.has_key?(field_name.to_s) then val = hsh[field_name.to_s]
|
90
109
|
else next ; end
|
91
|
-
write_attribute(
|
110
|
+
write_attribute(field_name, val)
|
92
111
|
end
|
93
112
|
self
|
94
113
|
end
|
@@ -103,9 +122,9 @@ module Gorillib
|
|
103
122
|
# @raise [UnknownAttributeError] if the attribute is unknown
|
104
123
|
# @return [Object] The value of the attribute, or nil if it is unset
|
105
124
|
def read_attribute(field_name)
|
106
|
-
|
107
|
-
if instance_variable_defined?(
|
108
|
-
instance_variable_get(
|
125
|
+
attr_name = "@#{field_name}"
|
126
|
+
if instance_variable_defined?(attr_name)
|
127
|
+
instance_variable_get(attr_name)
|
109
128
|
else
|
110
129
|
read_unset_attribute(field_name)
|
111
130
|
end
|
@@ -122,7 +141,6 @@ module Gorillib
|
|
122
141
|
# @raise [UnknownAttributeError] if the attribute is unknown
|
123
142
|
# @return [Object] the attribute's value
|
124
143
|
def write_attribute(field_name, val)
|
125
|
-
check_field(field_name)
|
126
144
|
instance_variable_set("@#{field_name}", val)
|
127
145
|
end
|
128
146
|
|
@@ -140,7 +158,6 @@ module Gorillib
|
|
140
158
|
# @raise [UnknownAttributeError] if the attribute is unknown
|
141
159
|
# @return [Object] the former value if it was set, nil if it was unset
|
142
160
|
def unset_attribute(field_name)
|
143
|
-
check_field(field_name)
|
144
161
|
if instance_variable_defined?("@#{field_name}")
|
145
162
|
val = instance_variable_get("@#{field_name}")
|
146
163
|
remove_instance_variable("@#{field_name}")
|
@@ -159,7 +176,6 @@ module Gorillib
|
|
159
176
|
# @raise [UnknownAttributeError] if the attribute is unknown
|
160
177
|
# @return [true, false]
|
161
178
|
def attribute_set?(field_name)
|
162
|
-
check_field(field_name)
|
163
179
|
instance_variable_defined?("@#{field_name}")
|
164
180
|
end
|
165
181
|
|
@@ -188,9 +204,8 @@ module Gorillib
|
|
188
204
|
def inspect_helper(detailed, attrs)
|
189
205
|
str = "#<" << self.class.name.to_s
|
190
206
|
if detailed && attrs.present?
|
191
|
-
str << " "
|
192
|
-
|
193
|
-
"#{attr}=#{val.is_a?(Gorillib::Model) || val.is_a?(Gorillib::Collection) ? val.inspect(false) : val.inspect}"
|
207
|
+
str << " " << attrs.map do |attr, val|
|
208
|
+
"#{attr}=#{val.is_a?(Gorillib::Model) || val.is_a?(Gorillib::GenericCollection) ? val.inspect(false) : val.inspect}"
|
194
209
|
end.join(", ")
|
195
210
|
end
|
196
211
|
str << ">"
|
@@ -199,17 +214,13 @@ module Gorillib
|
|
199
214
|
|
200
215
|
protected
|
201
216
|
|
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
217
|
module ClassMethods
|
210
218
|
|
219
|
+
#
|
220
|
+
# A readable handle for this field
|
221
|
+
#
|
211
222
|
def typename
|
212
|
-
Gorillib::Inflector.underscore(self.name).gsub(%r{/}, '.')
|
223
|
+
@typename ||= Gorillib::Inflector.underscore(self.name||'anon').gsub(%r{/}, '.')
|
213
224
|
end
|
214
225
|
|
215
226
|
#
|
@@ -219,12 +230,12 @@ module Gorillib
|
|
219
230
|
def receive(attrs={}, &block)
|
220
231
|
return nil if attrs.nil?
|
221
232
|
return attrs if attrs.is_a?(self)
|
222
|
-
|
233
|
+
#
|
234
|
+
Gorillib::Model::Validate.hashlike!(attrs){ "attributes for #{self.inspect}" }
|
223
235
|
klass = attrs.has_key?(:_type) ? Gorillib::Factory(attrs[:_type]) : self
|
224
|
-
warn "factory #{
|
225
|
-
|
226
|
-
|
227
|
-
obj
|
236
|
+
warn "factory #{klass} is not a type of #{self} as specified in #{attrs}" unless klass <= self
|
237
|
+
#
|
238
|
+
klass.new(attrs, &block)
|
228
239
|
end
|
229
240
|
|
230
241
|
# Defines a new field
|
@@ -260,12 +271,12 @@ module Gorillib
|
|
260
271
|
|
261
272
|
# @return [true, false] true if the field is defined on this class
|
262
273
|
def has_field?(field_name)
|
263
|
-
fields.has_key?(field_name
|
274
|
+
fields.has_key?(field_name)
|
264
275
|
end
|
265
276
|
|
266
277
|
# @return [Array<Symbol>] The attribute names
|
267
278
|
def field_names
|
268
|
-
fields.keys
|
279
|
+
@_field_names ||= fields.keys
|
269
280
|
end
|
270
281
|
|
271
282
|
# @return Class name and its attributes
|
@@ -284,7 +295,8 @@ module Gorillib
|
|
284
295
|
# are added after the child class is defined.
|
285
296
|
def _reset_descendant_fields
|
286
297
|
ObjectSpace.each_object(::Class) do |klass|
|
287
|
-
klass.__send__(:remove_instance_variable, '@_fields')
|
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')
|
288
300
|
end
|
289
301
|
end
|
290
302
|
|
@@ -306,8 +318,9 @@ module Gorillib
|
|
306
318
|
|
307
319
|
# define the present method `#foo?` for a field named `:foo`
|
308
320
|
def define_attribute_tester(field_name, field_type, visibility)
|
321
|
+
field = fields[field_name]
|
309
322
|
define_meta_module_method("#{field_name}?", visibility) do
|
310
|
-
attribute_set?(field_name)
|
323
|
+
attribute_set?(field_name) || field.has_default?
|
311
324
|
end
|
312
325
|
end
|
313
326
|
|