gorillib 0.4.0pre → 0.4.1pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|