Class: Ditz::ModelObject
Defined Under Namespace
Classes: ModelError
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
- #log(what, who, comment) ⇒ 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
Constructor Details
#initialize ⇒ ModelObject
Returns a new instance of ModelObject.
19 20 21 22 23 |
# File 'lib/ditz/model.rb', line 19 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.
162 163 164 |
# File 'lib/ditz/model.rb', line 162 def fields @fields end |
.serialized_values ⇒ Object (readonly)
Returns the value of attribute serialized_values.
162 163 164 |
# File 'lib/ditz/model.rb', line 162 def serialized_values @serialized_values end |
.values ⇒ Object (readonly)
Returns the value of attribute values.
162 163 164 |
# File 'lib/ditz/model.rb', line 162 def values @values end |
Class Method Details
.changes_are_logged ⇒ Object
165 166 167 168 |
# File 'lib/ditz/model.rb', line 165 def self.changes_are_logged define_method(:changes_are_logged?) { true } field :log_events, :multi => true, :ask => false 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
272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/ditz/model.rb', line 272 def create vals={}, generator_args=[] o = self.new @fields.each do |fname, fopts| val = if(x = vals[fname] || vals[fname.to_s]) x elsif(found, x = generate_field_value(o, fopts, generator_args, :interactive => false)) && found x elsif !fopts[:nil_ok] raise ModelError, "missing required field #{fname.inspect} on #{self.name} object (got #{vals.keys.inspect})" end o.send "#{fname}=", val if val end 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.
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/ditz/model.rb', line 242 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] elsif(found, v = generate_field_value(o, field_opts, generator_args, :interactive => true)) && 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_smartly 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 o.send "#{name}=", val end 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.
115 116 117 118 119 120 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 |
# File 'lib/ditz/model.rb', line 115 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
160 |
# File 'lib/ditz/model.rb', line 160 def self.field_names; @fields.map { |name, opts| name } end |
.from(fn) ⇒ Object
170 171 172 173 174 175 |
# File 'lib/ditz/model.rb', line 170 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= end end |
.inherited(subclass) ⇒ Object
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/ditz/model.rb', line 29 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 else $stderr.puts "warning: unknown field #{k.inspect} in YAML for #{type}; ignoring" 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
26 |
# File 'lib/ditz/model.rb', line 26 def self.yaml_domain; "ditz.rubyforge.org,2008-03-06" end |
.yaml_other_thing ⇒ Object
27 |
# File 'lib/ditz/model.rb', line 27 def self.yaml_other_thing; name.split('::').last.dcfirst end |
Instance Method Details
#changed! ⇒ Object
232 |
# File 'lib/ditz/model.rb', line 232 def changed!; @changed = true end |
#changed? ⇒ Boolean
231 |
# File 'lib/ditz/model.rb', line 231 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
55 56 57 |
# File 'lib/ditz/model.rb', line 55 def deserialized_form_of field, value value end |
#each_modelobject ⇒ Object
depth-first search on all reachable ModelObjects. fuck yeah.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/ditz/model.rb', line 184 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
181 |
# File 'lib/ditz/model.rb', line 181 def inspect; to_s end |
#log(what, who, comment) ⇒ Object
226 227 228 229 |
# File 'lib/ditz/model.rb', line 226 def log what, who, comment add_log_event([Time.now, who, what, comment || ""]) self end |
#save!(fn) ⇒ Object
203 204 205 206 207 |
# File 'lib/ditz/model.rb', line 203 def save! fn #FileUtils.mv fn, "#{fn}~", :force => true rescue nil File.open(fn, "w") { |f| f.puts to_yaml } self end |
#serialized_form_of(field, value) ⇒ Object
convert memory form => disk form
60 61 62 |
# File 'lib/ditz/model.rb', line 60 def serialized_form_of field, value value end |
#to_s ⇒ Object
177 178 179 |
# File 'lib/ditz/model.rb', line 177 def to_s "<#{self.class.name}: " + self.class.field_names.map { |f| "#{f}: " + send(f).inspect }.join(", ") + ">" end |
#to_yaml(opts = {}) ⇒ Object
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/ditz/model.rb', line 209 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
28 |
# File 'lib/ditz/model.rb', line 28 def to_yaml_type; "!#{self.class.yaml_domain}/#{self.class.yaml_other_thing}" end |
#unchanged! ⇒ Object
233 |
# File 'lib/ditz/model.rb', line 233 def unchanged!; @changed = false end |