Class: Ditz::ModelObject
Class Attribute Summary collapse
-
.fields ⇒ Object
readonly
Returns the value of attribute fields.
-
.serialized_values ⇒ Object
readonly
Returns the value of attribute serialized_values.
-
.values ⇒ Object
readonly
Returns the value of attribute values.
Class Method Summary collapse
- .changes_are_logged ⇒ Object
-
.create(vals = {}, generator_args = []) ⇒ Object
creates the object, filling in fields from ‘vals’, and throwing a ModelError when it can’t find all the requisite fields.
-
.create_interactively(opts = {}) ⇒ Object
creates the object, prompting the user when necessary.
-
.field(name, opts = {}) ⇒ Object
Add a field to a model object.
- .field_names ⇒ Object
- .from(fn) ⇒ Object
- .inherited(subclass) ⇒ Object
-
.yaml_domain ⇒ Object
yamlability.
- .yaml_other_thing ⇒ Object
Instance Method Summary collapse
- #changed! ⇒ Object
- #changed? ⇒ Boolean
-
#deserialized_form_of(field, value) ⇒ Object
override these two to model per-field transformations between disk and memory.
-
#each_modelobject ⇒ Object
depth-first search on all reachable ModelObjects.
-
#initialize ⇒ ModelObject
constructor
A new instance of ModelObject.
- #inspect ⇒ Object
- #save!(fn) ⇒ Object
-
#serialized_form_of(field, value) ⇒ Object
convert memory form => disk form.
- #to_s ⇒ Object
- #to_yaml(opts = {}) ⇒ Object
- #to_yaml_type ⇒ Object
- #unchanged! ⇒ Object
-
#validate!(whence, context) ⇒ Object
override me and throw ModelErrors if necessary.
Constructor Details
#initialize ⇒ ModelObject
Returns a new instance of ModelObject.
23 24 25 26 27 |
# File 'lib/ditz/model.rb', line 23 def initialize @values = {} @serialized_values = {} self.class.fields.map { |f, opts| @values[f] = [] if opts[:multi] } end |
Class Attribute Details
.fields ⇒ Object (readonly)
Returns the value of attribute fields.
168 169 170 |
# File 'lib/ditz/model.rb', line 168 def fields @fields end |
.serialized_values ⇒ Object (readonly)
Returns the value of attribute serialized_values.
168 169 170 |
# File 'lib/ditz/model.rb', line 168 def serialized_values @serialized_values end |
.values ⇒ Object (readonly)
Returns the value of attribute values.
168 169 170 |
# File 'lib/ditz/model.rb', line 168 def values @values end |
Class Method Details
.changes_are_logged ⇒ Object
171 172 173 174 175 176 177 178 179 |
# File 'lib/ditz/model.rb', line 171 def self.changes_are_logged define_method(:changes_are_logged?) { true } field :log_events, :multi => true, :ask => false define_method(:log) do |what, who, comment| add_log_event([Time.now, who, what, comment || ""]) self end define_method(:last_event_time) { log_events.empty? ? nil : log_events.last[0] } end |
.create(vals = {}, generator_args = []) ⇒ Object
creates the object, filling in fields from ‘vals’, and throwing a ModelError when it can’t find all the requisite fields
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/ditz/model.rb', line 284 def create vals={}, generator_args=[] o = self.new @fields.each do |fname, fopts| x = vals[fname] x = vals[fname.to_s] if x.nil? val = unless x.nil? x else found, x = generate_field_value(o, fopts, generator_args, :interactive => false) if found x elsif !fopts[:nil_ok] raise ModelError, "missing required field #{fname.inspect} on #{self.name} object (got #{vals.keys.inspect})" end end o.send "#{fname}=", val unless val.nil? end o.validate! :create, generator_args o end |
.create_interactively(opts = {}) ⇒ Object
creates the object, prompting the user when necessary. can take a :with => { hash } parameter for pre-filling model fields.
can also take a :defaults_from => obj parameter for pre-filling model fields from another object with (some of) those fields. kinda like a bizarre interactive copy constructor.
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/ditz/model.rb', line 250 def create_interactively opts={} o = self.new generator_args = opts[:args] || [] @fields.each do |name, field_opts| val = if opts[:with] && opts[:with][name] opts[:with][name] else found, v = generate_field_value(o, field_opts, generator_args, :interactive => true) if found v else q = field_opts[:prompt] || name.to_s.capitalize if field_opts[:multiline] ## multiline options currently aren't allowed to have a default ## value, so just ask. ask_multiline_or_editor q else default = if opts[:defaults_from] && opts[:defaults_from].respond_to?(name) && (x = opts[:defaults_from].send(name)) x else default = generate_field_default o, field_opts, generator_args end ask q, :default => default end end end o.send "#{name}=", val end o.validate! :create, generator_args o end |
.field(name, opts = {}) ⇒ Object
Add a field to a model object
The options you specify here determine how the field is populated when an instance of this object is created. Objects can be created interactively, with #create_interactively, or non-interactively, with #create, and the creation mode, combined with these options, determine how the field is populated on a new model object.
The default behavior is to simply prompt the user with the field name when in interactive mode, and to raise an exception if the value is not passed to #create in non-interactive mode.
Options:
:interactive_generator => a method name or Proc that will be called to
return the value of this field, if the model object is created
interactively.
:generator => a method name or Proc that will be called to return the
value of this field. If the model object is created interactively, and
a :interactive_generator option is specified, that will be used instead.
:multi => a boolean determining whether the field has multiple values,
i.e., is an array. If created with :ask => false, will be initialized
to [] instead of to nil. Additionally, the model object will have
#add_<field> and #drop_<field> methods.
:ask => a boolean determining whether, if the model object is created
interactively, the user will be prompted for the value of this field.
TRUE BY DEFAULT. If :interactive_generator or :generator are specified,
those will be called instead.
If this is true, non-interactive creation
will raise an exception unless the field value is passed as an argument.
If this is false, non-interactive creation will initialize this to nil
(or [] if this field is additionally marked :multi) unless the value is
passed as an argument.
:prompt => a string to display to the user when prompting for the field
value during interactive creation. Not used if :generator or
:interactive_generator is specified.
:multiline => a boolean determining whether to prompt the user for a
multiline answer during interactive creation. Default false. Not used
if :generator or :interactive_generator is specified.
:default => a default value when prompting for the field value during
interactive creation. Not used if :generator, :interactive_generator,
:multiline, or :default_generator is specified.
:default_generator => a method name or Proc which will be called to
generate the default value when prompting for the field value during
interactive creation. Not used if :generator, :interactive_generator,
or :multiline is specified.
:nil_ok => a boolean determining whether, if created in non-interactive
mode and the value for this field is not passed in, (or is passed in
as nil), that's ok. Default is false. This is not necessary if :ask =>
false is specified; it's only necessary for fields that you want an
interactive prompt for, but a nil value is fine.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/ditz/model.rb', line 121 def self.field name, opts={} @fields ||= [] # can't use a hash because we need to preserve field order raise ModelError, "field with name #{name} already defined" if @fields.any? { |k, v| k == name } @fields << [name, opts] if opts[:multi] single_name = name.to_s.sub(/s$/, "") # oh yeah define_method "add_#{single_name}" do |obj| array = send(name) raise ModelError, "already has a #{single_name} with name #{obj.name.inspect}" if obj.respond_to?(:name) && array.any? { |o| o.name == obj.name } changed! @serialized_values.delete name array << obj end define_method "drop_#{single_name}" do |obj| return unless send(name).delete obj @serialized_values.delete name changed! obj end end define_method "#{name}=" do |o| changed! @serialized_values.delete name @values[name] = o end define_method "__serialized_#{name}=" do |o| changed! @values.delete name @serialized_values[name] = o end define_method "__serialized_#{name}" do @serialized_values[name] end define_method name do return @values[name] if @values.member?(name) @values[name] = deserialized_form_of name, @serialized_values[name] end end |
.field_names ⇒ Object
166 |
# File 'lib/ditz/model.rb', line 166 def self.field_names; @fields.map { |name, opts| name } end |
.from(fn) ⇒ Object
181 182 183 184 185 186 187 |
# File 'lib/ditz/model.rb', line 181 def self.from fn returning YAML::load_file(fn) do |o| raise ModelError, "error loading from yaml file #{fn.inspect}: expected a #{self}, got a #{o.class}" unless o.class == self o.pathname = fn if o.respond_to? :pathname= o.validate! :load, [] end end |
.inherited(subclass) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/ditz/model.rb', line 37 def self.inherited subclass YAML.add_domain_type(yaml_domain, subclass.yaml_other_thing) do |type, val| o = subclass.new val.each do |k, v| m = "__serialized_#{k}=" if o.respond_to? m o.send m, v end end o.class.fields.each do |f, opts| m = "__serialized_#{f}" if opts[:multi] && o.send(m).nil? o.send(m + '=', []) end end o.unchanged! o end end |
.yaml_domain ⇒ Object
yamlability
34 |
# File 'lib/ditz/model.rb', line 34 def self.yaml_domain; "ditz.rubyforge.org,2008-03-06" end |
.yaml_other_thing ⇒ Object
35 |
# File 'lib/ditz/model.rb', line 35 def self.yaml_other_thing; name.split('::').last.dcfirst end |
Instance Method Details
#changed! ⇒ Object
240 |
# File 'lib/ditz/model.rb', line 240 def changed!; @changed = true end |
#changed? ⇒ Boolean
239 |
# File 'lib/ditz/model.rb', line 239 def changed?; @changed ||= false end |
#deserialized_form_of(field, value) ⇒ Object
override these two to model per-field transformations between disk and memory.
convert disk form => memory form
61 62 63 |
# File 'lib/ditz/model.rb', line 61 def deserialized_form_of field, value value end |
#each_modelobject ⇒ Object
depth-first search on all reachable ModelObjects. fuck yeah.
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/ditz/model.rb', line 196 def each_modelobject seen = {} to_see = [self] until to_see.empty? cur = to_see.pop seen[cur] = true yield cur cur.class.field_names.each do |f| val = cur.send(f) next if seen[val] if val.is_a?(ModelObject) to_see.push val elsif val.is_a?(Array) to_see += val.select { |v| v.is_a?(ModelObject) } end end end end |
#inspect ⇒ Object
193 |
# File 'lib/ditz/model.rb', line 193 def inspect; to_s end |
#save!(fn) ⇒ Object
215 216 217 218 219 220 |
# File 'lib/ditz/model.rb', line 215 def save! fn #FileUtils.mv fn, "#{fn}~", :force => true rescue nil File.open(fn, "w") { |f| f.puts to_yaml } unchanged! self end |
#serialized_form_of(field, value) ⇒ Object
convert memory form => disk form
66 67 68 |
# File 'lib/ditz/model.rb', line 66 def serialized_form_of field, value value end |
#to_s ⇒ Object
189 190 191 |
# File 'lib/ditz/model.rb', line 189 def to_s "<#{self.class.name}: " + self.class.field_names.map { |f| "#{f}: " + send(f).inspect }.join(", ") + ">" end |
#to_yaml(opts = {}) ⇒ Object
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/ditz/model.rb', line 222 def to_yaml opts={} ret = YAML::quick_emit(object_id, opts) do |out| out.map(taguri, nil) do |map| self.class.fields.each do |f, fops| v = if @serialized_values.member?(f) @serialized_values[f] else @serialized_values[f] = serialized_form_of f, @values[f] end map.add f.to_s, v end end end YamlWaml.decode(ret) end |
#to_yaml_type ⇒ Object
36 |
# File 'lib/ditz/model.rb', line 36 def to_yaml_type; "!#{self.class.yaml_domain}/#{self.class.yaml_other_thing}" end |
#unchanged! ⇒ Object
241 |
# File 'lib/ditz/model.rb', line 241 def unchanged!; @changed = false end |
#validate!(whence, context) ⇒ Object
override me and throw ModelErrors if necessary
30 31 |
# File 'lib/ditz/model.rb', line 30 def validate! whence, context end |