Class: Ditz::ModelObject

Inherits:
Object show all
Defined in:
lib/ditz/model.rb

Direct Known Subclasses

Component, Config, Issue, Label, Project, Release

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeModelObject

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

.fieldsObject (readonly)

Returns the value of attribute fields.



168
169
170
# File 'lib/ditz/model.rb', line 168

def fields
  @fields
end

.serialized_valuesObject (readonly)

Returns the value of attribute serialized_values.



168
169
170
# File 'lib/ditz/model.rb', line 168

def serialized_values
  @serialized_values
end

.valuesObject (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_loggedObject



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.

Raises:



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_namesObject



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_domainObject

yamlability



34
# File 'lib/ditz/model.rb', line 34

def self.yaml_domain; "ditz.rubyforge.org,2008-03-06" end

.yaml_other_thingObject



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

Returns:

  • (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_modelobjectObject

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

#inspectObject



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_sObject



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_typeObject



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