Class: Ditz::ModelObject

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

Direct Known Subclasses

Component, Config, Issue, Project, Release

Defined Under Namespace

Classes: ModelError

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeModelObject

Returns a new instance of ModelObject.



18
19
20
21
22
# File 'lib/model.rb', line 18

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.



105
106
107
# File 'lib/model.rb', line 105

def fields
  @fields
end

.serialized_valuesObject (readonly)

Returns the value of attribute serialized_values.



105
106
107
# File 'lib/model.rb', line 105

def serialized_values
  @serialized_values
end

.valuesObject (readonly)

Returns the value of attribute values.



105
106
107
# File 'lib/model.rb', line 105

def values
  @values
end

Class Method Details

.changes_are_loggedObject



108
109
110
111
# File 'lib/model.rb', line 108

def self.changes_are_logged
  define_method(:changes_are_logged?) { true }
  field :log_events, :multi => true, :ask => false
end

.create(generator_args, vals = {}) ⇒ Object

creates the object, filling in fields from ‘vals’, and throwing a ModelError when it can’t find all the requisite fields



222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/model.rb', line 222

def create generator_args, vals={}
  o = self.new
  @fields.each do |name, opts|
    val = if vals[name]
      vals[name]
    elsif(found, x = generate_field_value(o, opts, generator_args)) && found
      x
    else
      raise ModelError, "missing required field #{name}"
    end
    o.send "#{name}=", 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.



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/model.rb', line 192

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, x = generate_field_value(o, field_opts, generator_args)) && found
      x
    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 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 new field to a model object

Raises:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/model.rb', line 58

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 @values[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



103
# File 'lib/model.rb', line 103

def self.field_names; @fields.map { |name, opts| name } end

.from(fn) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/model.rb', line 113

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.class.fields.each do |f, opts|
      m = "__serialized_#{f}"
      if opts[:multi] && o.send(m).nil?
        $stderr.puts "Warning: corrected nil multi-field #{f}"
        o.send "#{m}=", []
      end
    end
  end
end

.inherited(subclass) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/model.rb', line 28

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.unchanged!
    o
  end
end

.yaml_domainObject

yamlability



25
# File 'lib/model.rb', line 25

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

.yaml_other_thingObject



26
# File 'lib/model.rb', line 26

def self.yaml_other_thing; name.split('::').last.dcfirst end

Instance Method Details

#changed!Object



182
# File 'lib/model.rb', line 182

def changed!; @changed = true end

#changed?Boolean

Returns:

  • (Boolean)


181
# File 'lib/model.rb', line 181

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



48
49
50
# File 'lib/model.rb', line 48

def deserialized_form_of field, value
  @serialized_values[field]
end

#each_modelobjectObject

depth-first search on all reachable ModelObjects. fuck yeah.



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/model.rb', line 135

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



132
# File 'lib/model.rb', line 132

def inspect; to_s end

#log(what, who, comment) ⇒ Object



176
177
178
179
# File 'lib/model.rb', line 176

def log what, who, comment
  add_log_event([Time.now, who, what, comment || ""])
  self
end

#save!(fn) ⇒ Object



154
155
156
157
158
# File 'lib/model.rb', line 154

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



53
54
55
# File 'lib/model.rb', line 53

def serialized_form_of field, value
  @values[field]
end

#to_sObject



128
129
130
# File 'lib/model.rb', line 128

def to_s
  "<#{self.class.name}: " + self.class.field_names.map { |f| "#{f}: " + (@values[f].to_s || @serialized_values[f]).inspect }.join(", ") + ">"
end

#to_yaml(opts = {}) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/model.rb', line 160

def to_yaml opts={}
  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
end

#to_yaml_typeObject



27
# File 'lib/model.rb', line 27

def to_yaml_type; "!#{self.class.yaml_domain}/#{self.class.yaml_other_thing}" end

#unchanged!Object



183
# File 'lib/model.rb', line 183

def unchanged!; @changed = false end