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.



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

def fields
  @fields
end

.serialized_valuesObject (readonly)

Returns the value of attribute serialized_values.



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

def serialized_values
  @serialized_values
end

.valuesObject (readonly)

Returns the value of attribute values.



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

def values
  @values
end

Class Method Details

.changes_are_loggedObject



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

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

.create_interactively(opts = {}) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/model.rb', line 173

def self.create_interactively opts={}
  o = self.new
  args = opts[:args] || []
  @fields.each do |name, field_opts|
    val = if opts[:with] && opts[:with][name]
      opts[:with][name]
    elsif field_opts[:generator].is_a? Proc
      field_opts[:generator].call(*args)
    elsif field_opts[:generator]
      o.send field_opts[:generator], *args
    elsif field_opts[:ask] == false # nil counts as true here
      field_opts[:default] || (field_opts[:multi] ? [] : nil)
    else
      q = field_opts[:prompt] || name.to_s.capitalize
      if field_opts[:multiline]
        ask_multiline q
      else
        default = if field_opts[:default_generator].is_a? Proc
          field_opts[:default_generator].call(*args)
        elsif field_opts[:default_generator]
          o.send field_opts[:default_generator], *args
        elsif field_opts[:default]
          field_opts[:default]
        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
# 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 name do
    return @values[name] if @values.member?(name)
    @values[name] = deserialized_form_of name, @serialized_values[name]
  end
end

.field_namesObject



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

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

.from(fn) ⇒ Object



109
110
111
112
113
114
# File 'lib/model.rb', line 109

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



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



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

def changed!; @changed = true end

#changed?Boolean

Returns:

  • (Boolean)


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

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.



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/model.rb', line 123

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



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

def inspect; to_s end

#log(what, who, comment) ⇒ Object



164
165
166
167
# File 'lib/model.rb', line 164

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

#save!(fn) ⇒ Object



142
143
144
145
146
# File 'lib/model.rb', line 142

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



116
117
118
# File 'lib/model.rb', line 116

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



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/model.rb', line 148

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



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

def unchanged!; @changed = false end