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.6"
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.



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

def format
  @format
end

Class Method Details

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



294
295
296
# File 'lib/storable.rb', line 294

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.



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

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



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

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.



93
94
95
# File 'lib/storable.rb', line 93

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.



250
251
252
# File 'lib/storable.rb', line 250

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.



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/storable.rb', line 257

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.



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

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.



133
134
135
136
137
# File 'lib/storable.rb', line 133

def self.from_hash(from={})
  return nil if !from || from.empty?
  me = self.new
  me.from_hash(from)
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).



207
208
209
210
211
212
213
214
215
216
# File 'lib/storable.rb', line 207

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.



245
246
247
# File 'lib/storable.rb', line 245

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).



195
196
197
198
199
200
# File 'lib/storable.rb', line 195

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

.has_field?(n) ⇒ Boolean

Returns:

  • (Boolean)


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

def self.has_field?(n)
  field_names.member? n.to_sym
end

.read_file_to_array(path) ⇒ Object



279
280
281
282
283
284
285
286
287
288
# File 'lib/storable.rb', line 279

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



290
291
292
# File 'lib/storable.rb', line 290

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



298
299
300
301
302
303
304
305
306
307
# File 'lib/storable.rb', line 298

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.



103
104
105
106
107
108
# File 'lib/storable.rb', line 103

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



88
89
90
# File 'lib/storable.rb', line 88

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.



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

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

#from_hash(from = {}) ⇒ Object



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
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/storable.rb', line 139

def from_hash(from={})
  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.) -- circa 2008-09-15
    
    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
    
    self.send("#{key}=", value) if self.respond_to?("#{key}=")  
  end

  self.postprocess
  self
end

#has_field?(n) ⇒ Boolean

Returns:

  • (Boolean)


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

def has_field?(n)
  self.class.field_names.member? n.to_sym
end

#postprocessObject



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

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)



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

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.



224
225
226
227
228
229
230
231
232
# File 'lib/storable.rb', line 224

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.



124
125
126
127
128
129
130
# File 'lib/storable.rb', line 124

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.



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

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



217
218
219
# File 'lib/storable.rb', line 217

def to_json(with_titles=true)
  to_hash.to_json
end

#to_string(*args) ⇒ Object



110
111
112
# File 'lib/storable.rb', line 110

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)



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

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

#to_yaml(with_titles = true) ⇒ Object



201
202
203
# File 'lib/storable.rb', line 201

def to_yaml(with_titles=true)
  to_hash.to_yaml
end