class Ditz::ModelObject
Attributes
Public Class Methods
# 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
creates the object, filling in fields from 'vals', and throwing a ModelError
when it can't find all the requisite fields
# 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
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.
# 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
add a new field to a model object
# 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
# File lib/model.rb, line 103 def self.field_names; @fields.map { |name, opts| name } end
# 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
# 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
# File lib/model.rb, line 18 def initialize @values = {} @serialized_values = {} self.class.fields.map { |f, opts| @values[f] = [] if opts[:multi] } end
yamlability
# File lib/model.rb, line 25 def self.yaml_domain; "ditz.rubyforge.org,2008-03-06" end
# File lib/model.rb, line 26 def self.yaml_other_thing; name.split('::').last.dcfirst end
Private Class Methods
# File lib/model.rb, line 253 def generate_field_default o, opts, args if opts[:default_generator].is_a? Proc opts[:default_generator].call(*args) elsif opts[:default_generator] o.send opts[:default_generator], *args elsif opts[:default] opts[:default] end end
get the value for a field if it can be automatically determined returns [success, value] (because a successful value can be ni)
# File lib/model.rb, line 241 def generate_field_value o, opts, args if opts[:generator].is_a? Proc [true, opts[:generator].call(*args)] elsif opts[:generator] [true, o.send(opts[:generator], *args)] elsif opts[:ask] == false # nil counts as true here [true, opts[:default] || (opts[:multi] ? [] : nil)] else [false, nil] end end
Public Instance Methods
# File lib/model.rb, line 182 def changed!; @changed = true end
# File lib/model.rb, line 181 def changed?; @changed ||= false end
override these two to model per-field transformations between disk and memory.
convert disk form => memory form
# File lib/model.rb, line 48 def deserialized_form_of field, value @serialized_values[field] end
depth-first search on all reachable ModelObjects. fuck yeah.
# 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
# File lib/model.rb, line 132 def inspect; to_s end
# File lib/model.rb, line 176 def log what, who, comment add_log_event([Time.now, who, what, comment || ""]) self end
# 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
convert memory form => disk form
# File lib/model.rb, line 53 def serialized_form_of field, value @values[field] end
# 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
# 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
# File lib/model.rb, line 27 def to_yaml_type; "!#{self.class.yaml_domain}/#{self.class.yaml_other_thing}" end
# File lib/model.rb, line 183 def unchanged!; @changed = false end