gorillib 0.4.0pre → 0.4.1pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/CHANGELOG.md +36 -1
  2. data/Gemfile +23 -19
  3. data/Guardfile +1 -1
  4. data/Rakefile +31 -31
  5. data/TODO.md +2 -30
  6. data/VERSION +1 -1
  7. data/examples/builder/ironfan.rb +4 -4
  8. data/gorillib.gemspec +40 -25
  9. data/lib/gorillib/array/average.rb +13 -0
  10. data/lib/gorillib/array/sorted_median.rb +11 -0
  11. data/lib/gorillib/array/sorted_percentile.rb +11 -0
  12. data/lib/gorillib/array/sorted_sample.rb +12 -0
  13. data/lib/gorillib/builder.rb +8 -14
  14. data/lib/gorillib/collection/has_collection.rb +31 -31
  15. data/lib/gorillib/collection/list_collection.rb +58 -0
  16. data/lib/gorillib/collection/model_collection.rb +63 -0
  17. data/lib/gorillib/collection.rb +57 -85
  18. data/lib/gorillib/logger/log.rb +26 -22
  19. data/lib/gorillib/model/base.rb +52 -39
  20. data/lib/gorillib/model/doc_string.rb +15 -0
  21. data/lib/gorillib/model/factories.rb +56 -61
  22. data/lib/gorillib/model/lint.rb +24 -0
  23. data/lib/gorillib/model/serialization.rb +12 -2
  24. data/lib/gorillib/model/validate.rb +2 -2
  25. data/lib/gorillib/pathname.rb +21 -6
  26. data/lib/gorillib/some.rb +2 -0
  27. data/lib/gorillib/type/extended.rb +0 -2
  28. data/lib/gorillib/type/url.rb +9 -0
  29. data/lib/gorillib/utils/console.rb +4 -1
  30. data/notes/HOWTO.md +22 -0
  31. data/notes/bucket.md +155 -0
  32. data/notes/builder.md +170 -0
  33. data/notes/collection.md +81 -0
  34. data/notes/factories.md +86 -0
  35. data/notes/model-overlay.md +209 -0
  36. data/notes/model.md +135 -0
  37. data/notes/structured-data-classes.md +127 -0
  38. data/spec/array/average_spec.rb +24 -0
  39. data/spec/array/sorted_median_spec.rb +18 -0
  40. data/spec/array/sorted_percentile_spec.rb +24 -0
  41. data/spec/array/sorted_sample_spec.rb +28 -0
  42. data/spec/gorillib/builder_spec.rb +46 -28
  43. data/spec/gorillib/collection_spec.rb +195 -10
  44. data/spec/gorillib/model/lint_spec.rb +28 -0
  45. data/spec/gorillib/model/record/factories_spec.rb +27 -13
  46. data/spec/gorillib/model/serialization_spec.rb +3 -5
  47. data/spec/gorillib/model_spec.rb +86 -104
  48. data/spec/spec_helper.rb +2 -1
  49. data/spec/support/gorillib_test_helpers.rb +83 -7
  50. data/spec/support/model_test_helpers.rb +9 -28
  51. metadata +52 -44
  52. data/lib/gorillib/configurable.rb +0 -28
  53. data/spec/gorillib/configurable_spec.rb +0 -62
  54. 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
@@ -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
- # Merge the new items into a new collection; given items clobber existing items
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
- str = "c{ "
88
- if detailed
89
- str << clxn.map do |key, val|
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
- alias_method(:as_json, :to_wire)
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
- p [self, options]
105
- to_wire(*args).to_json(*args)
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
- protected
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
- def convert_value(val)
111
- return val unless factory
112
- return nil if val.nil?
113
- factory.receive(val)
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
- # - if the given collection responds_to `to_hash`, it is merged into the internal collection; each hash key *must* match the id of its value or results are undefined.
117
- # - otherwise, it merges a hash generates from the id/value pairs of each object in the given collection.
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
- cc.inject({}) do |acc, val|
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
@@ -1,29 +1,12 @@
1
- require 'logger'
2
-
3
1
  #
4
2
  # A convenient logger.
5
3
  #
6
- # define Log yourself to prevent its creation
4
+ # to override its creation, simply define the top-level constant `::Log`
7
5
  #
8
- ::Log = Logger.new($stderr) unless defined?(::Log)
9
-
10
- # unless defined?(Log)
11
- # require 'log4r'
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
@@ -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) 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])
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
- handle_extra_attributes( hsh.reject{|field_name,val| self.class.has_field?(field_name) } )
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", 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]
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(attr, val)
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
- check_field(field_name)
107
- if instance_variable_defined?("@#{field_name}")
108
- instance_variable_get("@#{field_name}")
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
- str << attrs.map do |attr, val|
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
- Gorillib::Model::Validate.hashlike!("attributes for #{self}", attrs)
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 #{self} doesn't match type specified in #{attrs}" unless klass <= self
225
- obj = klass.new
226
- obj.receive!(attrs, &block)
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.to_sym)
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') if klass <= self && klass.instance_variable_defined?('@_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
 
@@ -0,0 +1,15 @@
1
+ module Gorillib
2
+ module Model
3
+ module DocString
4
+ extend Gorillib::Concern
5
+ included do
6
+ field :doc, String
7
+
8
+ def doc(*args)
9
+ if s
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end