Class: Storable

Inherits:
Object
  • Object
show all
Defined in:
lib/storable.rb

Overview

Storable makes data available in multiple formats and can re-create objects from files. Fields are defined using the Storable.field method which tells Storable the order and name.

Defined Under Namespace

Classes: OrderedHash

Constant Summary collapse

VERSION =
0.5
NICE_TIME_FORMAT =
"%Y-%m-%d@%H:%M:%S".freeze
SUPPORTED_FORMATS =
[:tsv, :csv, :yaml, :json, :s, :string].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#formatObject

This value will be used as a default unless provided on-the-fly. See SUPPORTED_FORMATS for available values.



32
33
34
# File 'lib/storable.rb', line 32

def format
  @format
end

Class Method Details

.append_file(path, content, flush = true) ⇒ Object



283
284
285
# File 'lib/storable.rb', line 283

def self.append_file(path, content, flush=true)
  write_or_append_file('a', path, content, flush)
end

.field(args = {}) ⇒ Object

Accepts field definitions in the one of the follow formats:

field :product
field :product => Integer

The order they’re defined determines the order the will be output. The fields data is available by the standard accessors, class.product and class.product= etc… The value of the field will be cast to the type (if provided) when read from a file. The value is not touched when the type is not provided.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/storable.rb', line 55

def self.field(args={})
  # TODO: Examine casting from: http://codeforpeople.com/lib/ruby/fattr/fattr-1.0.3/
  args = {args => nil} unless args.kind_of?(Hash)

  args.each_pair do |m,t|
    
    [[:@@field_names, m], [:@@field_types, t]].each do |tuple|
      class_variable_set(tuple[0], []) unless class_variable_defined?(tuple[0])
      class_variable_set(tuple[0], class_variable_get(tuple[0]) << tuple[1])
    end
    
    next if method_defined?(m)
    
    define_method(m) do instance_variable_get("@#{m}") end
    define_method("#{m}=") do |val| 
      instance_variable_set("@#{m}",val)
    end
  end
end

.field_namesObject

Returns an array of field names defined by self.field



76
77
78
# File 'lib/storable.rb', line 76

def self.field_names
  class_variable_get(:@@field_names)
end

.field_typesObject

Returns an array of field types defined by self.field. Fields that did not receive a type are set to nil.



85
86
87
# File 'lib/storable.rb', line 85

def self.field_types
  class_variable_get(:@@field_types)
end

.from_csv(from = []) ⇒ Object

Create a new instance of the object from comma-delimited data. from a JSON string split into an array by line.



239
240
241
# File 'lib/storable.rb', line 239

def self.from_csv(from=[])
  self.from_delimited(from, ',')
end

.from_delimited(from = [], delim = ',') ⇒ Object

Create a new instance of the object from a delimited string. from a JSON string split into an array by line. delim is the field delimiter.



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/storable.rb', line 246

def self.from_delimited(from=[],delim=',')
  return if from.empty?
  # We grab an instance of the class so we can 
  hash = {}
  
  fnames = values = []
  if (from.size > 1 && !from[1].empty?)
    fnames = from[0].chomp.split(delim)
    values = from[1].chomp.split(delim)
  else
    fnames = self.field_names
    values = from[0].chomp.split(delim)
  end
  
  fnames.each_with_index do |key,index|
    next unless values[index]
    hash[key.to_sym] = values[index]
  end
  hash = from_hash(hash) if hash.kind_of?(Hash) 
  hash
end

.from_file(file_path, format = 'yaml') ⇒ Object

Create a new instance of the object using data from file.



107
108
109
110
111
112
113
114
# File 'lib/storable.rb', line 107

def self.from_file(file_path, format='yaml')
  raise "Cannot read file (#{file_path})" unless File.exists?(file_path)
  raise "#{self} doesn't support from_#{format}" unless self.respond_to?("from_#{format}")
  format = format || File.extname(file_path).tr('.', '')
  me = send("from_#{format}", read_file_to_array(file_path))
  me.format = format
  me
end

.from_hash(from = {}) ⇒ Object

Create a new instance of the object from a hash.



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
165
166
167
168
169
170
171
# File 'lib/storable.rb', line 125

def self.from_hash(from={})
  return nil if !from || from.empty?
  me = self.new
      
  fnames = field_names
  fnames.each_with_index do |key,index|
    
    stored_value = from[key] || from[key.to_s] # support for symbol keys and string keys
    
    # TODO: Correct this horrible implementation (sorry, me. It's just one of those days.)
    
    if field_types[index] == Array
      ((value ||= []) << stored_value).flatten 
    elsif field_types[index].kind_of?(Hash)
      
      value = stored_value
    else
      
      # SimpleDB stores attribute shit as lists of values
      ##value = stored_value.first if stored_value.is_a?(Array) && stored_value.size == 1
      value = (stored_value.is_a?(Array) && stored_value.size == 1) ? stored_value.first : stored_value
      
      if field_types[index] == Time
        value = Time.parse(value)
      elsif field_types[index] == DateTime
        value = DateTime.parse(value)
      elsif field_types[index] == TrueClass
        value = (value.to_s == "true")
      elsif field_types[index] == Float
        value = value.to_f
      elsif field_types[index] == Integer
        value = value.to_i
      elsif field_types[index].kind_of?(Storable) && stored_value.kind_of?(Hash)
        # I don't know why this is here so I'm going to raise an exception
        # and wait a while for an error in one of my other projects. 
        #value = field_types[index].from_hash(stored_value)
        raise "Delano, delano, delano. Clean up Storable!"
      end
    end
    
    me.send("#{key}=", value) if self.method_defined?("#{key}=")  
  end
  
  me.postprocess
  
  me
end

.from_json(*from) ⇒ Object

Create a new instance of the object from a JSON string. from a YAML String or Array (split into by line).



196
197
198
199
200
201
202
203
204
205
# File 'lib/storable.rb', line 196

def self.from_json(*from)
  from_str = [from].flatten.compact.join('')
  tmp = JSON::load(from_str)
  hash_sym = tmp.keys.inject({}) do |hash, key|
     hash[key.to_sym] = tmp[key]
     hash
  end
  hash_sym = from_hash(hash_sym) if hash_sym.kind_of?(Hash)  
  hash_sym
end

.from_tsv(from = []) ⇒ Object

Create a new instance from tab-delimited data.

from a JSON string split into an array by line.



234
235
236
# File 'lib/storable.rb', line 234

def self.from_tsv(from=[])
  self.from_delimited(from, "\t")
end

.from_yaml(*from) ⇒ Object

Create a new instance of the object from YAML. from a YAML String or Array (split into by line).



184
185
186
187
188
189
# File 'lib/storable.rb', line 184

def self.from_yaml(*from)
  from_str = [from].flatten.compact.join('')
  hash = YAML::load(from_str)
  hash = from_hash(hash) if hash.kind_of?(Hash)
  hash
end

.read_file_to_array(path) ⇒ Object



268
269
270
271
272
273
274
275
276
277
# File 'lib/storable.rb', line 268

def self.read_file_to_array(path)
  contents = []
  return contents unless File.exists?(path)
  
  open(path, 'r') do |l|
    contents = l.readlines
  end

  contents
end

.write_file(path, content, flush = true) ⇒ Object



279
280
281
# File 'lib/storable.rb', line 279

def self.write_file(path, content, flush=true)
  write_or_append_file('w', path, content, flush)
end

.write_or_append_file(write_or_append, path, content = '', flush = true) ⇒ Object



287
288
289
290
291
292
293
294
295
296
# File 'lib/storable.rb', line 287

def self.write_or_append_file(write_or_append, path, content = '', flush = true)
  #STDERR.puts "Writing to #{ path }..." 
  create_dir(File.dirname(path))
  
  open(path, write_or_append) do |f| 
    f.puts content
    f.flush if flush;
  end
  File.chmod(0600, path)
end

Instance Method Details

#dump(format = nil, with_titles = false) ⇒ Object

Dump the object data to the given format.



95
96
97
98
99
100
# File 'lib/storable.rb', line 95

def dump(format=nil, with_titles=false)
  format &&= format.to_sym
  format ||= 's' # as in, to_s
  raise "Format not defined (#{format})" unless SUPPORTED_FORMATS.member?(format)
  send("to_#{format}", with_titles) 
end

#field_namesObject

Returns an array of field names defined by self.field



80
81
82
# File 'lib/storable.rb', line 80

def field_names
  self.class.send(:class_variable_get, :@@field_names)
end

#field_typesObject

Returns an array of field types defined by self.field. Fields that did not receive a type are set to nil.



90
91
92
# File 'lib/storable.rb', line 90

def field_types
  self.class.send(:class_variable_get, :@@field_types)
end

#postprocessObject



41
42
# File 'lib/storable.rb', line 41

def postprocess
end

#to_csv(with_titles = false) ⇒ Object

Return the object data as a comma delimited string. with_titles specifiy whether to include field names (default: false)



229
230
231
# File 'lib/storable.rb', line 229

def to_csv(with_titles=false)
  to_delimited(with_titles, ',')
end

#to_delimited(with_titles = false, delim = ',') ⇒ Object

Return the object data as a delimited string. with_titles specifiy whether to include field names (default: false) delim is the field delimiter.



213
214
215
216
217
218
219
220
221
# File 'lib/storable.rb', line 213

def to_delimited(with_titles=false, delim=',')
  values = []
  field_names.each do |fname|
    values << self.send(fname.to_s)   # TODO: escape values
  end
  output = values.join(delim)
  output = field_names.join(delim) << $/ << output if with_titles
  output
end

#to_file(file_path = nil, with_titles = true) ⇒ Object

Write the object data to the given file.



116
117
118
119
120
121
122
# File 'lib/storable.rb', line 116

def to_file(file_path=nil, with_titles=true)
  raise "Cannot store to nil path" if file_path.nil?
  format = File.extname(file_path).tr('.', '')
  format &&= format.to_sym
  format ||= @format
  Storable.write_file(file_path, dump(format, with_titles))
end

#to_hash(with_titles = true) ⇒ Object

Return the object data as a hash with_titles is ignored.



174
175
176
177
178
179
180
# File 'lib/storable.rb', line 174

def to_hash(with_titles=true)
  tmp = USE_ORDERED_HASH ? Storable::OrderedHash.new : {}
  field_names.each do |fname|
    tmp[fname] = self.send(fname)
  end
  tmp
end

#to_json(with_titles = true) ⇒ Object



206
207
208
# File 'lib/storable.rb', line 206

def to_json(with_titles=true)
  to_hash.to_json
end

#to_string(*args) ⇒ Object



102
103
104
# File 'lib/storable.rb', line 102

def to_string(*args)
  to_s(*args)
end

#to_tsv(with_titles = false) ⇒ Object

Return the object data as a tab delimited string. with_titles specifiy whether to include field names (default: false)



224
225
226
# File 'lib/storable.rb', line 224

def to_tsv(with_titles=false)
  to_delimited(with_titles, "\t")
end

#to_yaml(with_titles = true) ⇒ Object



190
191
192
# File 'lib/storable.rb', line 190

def to_yaml(with_titles=true)
  to_hash.to_yaml
end