Class: Storable
- Inherits:
-
Object
- Object
- Storable
- Extended by:
- DefaultProcessors
- Defined in:
- lib/storable.rb,
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
Modules: DefaultProcessors Classes: Anonymous, OrderedHash
Constant Summary collapse
- USE_ORDERED_HASH =
(RUBY_VERSION =~ /^1.9/).nil?
- VERSION =
"0.8.9"
- NICE_TIME_FORMAT =
"%Y-%m-%d@%H:%M:%S".freeze
- SUPPORTED_FORMATS =
[:tsv, :csv, :yaml, :json, :s, :string].freeze
Class Attribute Summary collapse
-
.debug ⇒ Object
Returns the value of attribute debug.
-
.field_names ⇒ Object
Returns the value of attribute field_names.
-
.field_opts ⇒ Object
Returns the value of attribute field_opts.
-
.field_types ⇒ Object
Returns the value of attribute field_types.
-
.sensitive_fields(*args) ⇒ Object
Returns the value of attribute sensitive_fields.
Instance Attribute Summary collapse
-
#format ⇒ Object
This value will be used as a default unless provided on-the-fly.
Class Method Summary collapse
- .append_file(path, content, flush = true) ⇒ Object
-
.field(*args, &processor) ⇒ Object
Accepts field definitions in the one of the follow formats:.
- .from_array(*from) ⇒ Object
-
.from_csv(from = [], sensitive = false) ⇒ Object
Create a new instance of the object from comma-delimited data.
-
.from_delimited(from = [], delim = ',', sensitive = false) ⇒ Object
Create a new instance of the object from a delimited string.
-
.from_file(file_path, format = 'yaml') ⇒ Object
Create a new instance of the object using data from file.
-
.from_hash(from = {}) ⇒ Object
Create a new instance of the object from a hash.
-
.from_json(*from) ⇒ Object
Create a new instance of the object from a JSON string.
-
.from_tsv(from = [], sensitive = false) ⇒ Object
Create a new instance from tab-delimited data.
-
.from_yaml(*from) ⇒ Object
Create a new instance of the object from YAML.
- .has_field?(n) ⇒ Boolean
-
.inherited(obj) ⇒ Object
Passes along fields to inherited classes.
- .read_file_to_array(path) ⇒ Object
- .sensitive_field?(name) ⇒ Boolean
- .write_file(path, content, flush = true) ⇒ Object
- .write_or_append_file(write_or_append, path, content = '', flush = true) ⇒ Object
Instance Method Summary collapse
- #call(fname) ⇒ Object
-
#dump(format = nil, with_titles = false) ⇒ Object
Dump the object data to the given format.
-
#field_names ⇒ Object
Returns an array of field names defined by self.field.
-
#field_types ⇒ Object
Returns an array of field types defined by self.field.
- #from_array(*from) ⇒ Object
- #from_hash(from = {}) ⇒ Object
- #has_field?(n) ⇒ Boolean
- #has_processor?(fname) ⇒ Boolean
- #init(*args) ⇒ Object
-
#initialize(*args) ⇒ Storable
constructor
A new instance of Storable.
- #postprocess ⇒ Object
- #process(fname, val) ⇒ Object
- #sensitive! ⇒ Object
- #sensitive? ⇒ Boolean
- #sensitive_fields ⇒ Object
- #to_array ⇒ Object
-
#to_csv(with_titles = false) ⇒ Object
Return the object data as a comma delimited string.
-
#to_delimited(with_titles = false, delim = ',') ⇒ Object
Return the object data as a delimited string.
-
#to_file(file_path = nil, with_titles = true) ⇒ Object
Write the object data to the given file.
-
#to_hash ⇒ Object
Return the object data as a hash
with_titles
is ignored. - #to_json(*from, &blk) ⇒ Object
- #to_string(*args) ⇒ Object
-
#to_tsv(with_titles = false) ⇒ Object
Return the object data as a tab delimited string.
- #to_yaml(*from, &blk) ⇒ Object
Methods included from DefaultProcessors
gibbler_id_processor, hash_proc_processor, proc_processor
Constructor Details
#initialize(*args) ⇒ Storable
Returns a new instance of Storable.
228 229 230 |
# File 'lib/storable.rb', line 228 def initialize *args init *args end |
Class Attribute Details
.debug ⇒ Object
Returns the value of attribute debug.
45 46 47 |
# File 'lib/storable.rb', line 45 def debug @debug end |
.field_names ⇒ Object
Returns the value of attribute field_names.
45 46 47 |
# File 'lib/storable.rb', line 45 def field_names @field_names end |
.field_opts ⇒ Object
Returns the value of attribute field_opts.
45 46 47 |
# File 'lib/storable.rb', line 45 def field_opts @field_opts end |
.field_types ⇒ Object
Returns the value of attribute field_types.
45 46 47 |
# File 'lib/storable.rb', line 45 def field_types @field_types end |
.sensitive_fields(*args) ⇒ Object
Returns the value of attribute sensitive_fields.
45 46 47 |
# File 'lib/storable.rb', line 45 def sensitive_fields @sensitive_fields end |
Instance Attribute Details
#format ⇒ Object
This value will be used as a default unless provided on-the-fly. See SUPPORTED_FORMATS for available values.
150 151 152 |
# File 'lib/storable.rb', line 150 def format @format end |
Class Method Details
.append_file(path, content, flush = true) ⇒ Object
478 479 480 |
# File 'lib/storable.rb', line 478 def self.append_file(path, content, flush=true) write_or_append_file('a', path, content, flush) end |
.field(*args, &processor) ⇒ Object
Accepts field definitions in the one of the follow formats:
field :product
field :product => Integer
field :product do |val|
# modify val before it's stored.
end
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.
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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/storable.rb', line 69 def self.field(*args, &processor) # TODO: Examine casting from: http://codeforpeople.com/lib/ruby/fattr/fattr-1.0.3/ field_definitions = {} if args.first.kind_of?(Hash) args.first.each_pair do |fname,klass| field_definitions[fname] = { :class => klass } end else fname, opts = *args if opts.nil? field_definitions[fname] = {} elsif Hash === opts field_definitions[fname] = opts else raise ArgumentError, "Second argument must be a hash" end end self.field_names ||= [] self.field_types ||= {} self.field_opts ||= {} field_definitions.each_pair do |fname,opts| self.field_names << fname self.field_opts[fname] = opts self.field_types[fname] = opts[:class] unless opts[:class].nil? # This processor automatically converts a Proc object # to a String of its source. processor = proc_processor if opts[:class] == Proc && processor.nil? unless processor.nil? define_method("_storable_processor_#{fname}", &processor) end if method_defined?(fname) # don't redefine the getter method STDERR.puts "method exists: #{self}##{fname}" if Storable.debug else define_method(fname) do ret = instance_variable_get("@#{fname}") if ret.nil? if opts[:default] ret = opts[:default] elsif opts[:meth] ret = self.send(opts[:meth]) end end ret end end if method_defined?("#{fname}=") # don't redefine the setter methods STDERR.puts "method exists: #{self}##{fname}=" if Storable.debug else define_method("#{fname}=") do |val| instance_variable_set("@#{fname}",val) end end end end |
.from_array(*from) ⇒ Object
239 240 241 242 243 244 245 246 |
# File 'lib/storable.rb', line 239 def self.from_array *from from = from.flatten.compact return nil if !from || from.empty? me = new me.from_array *from me.postprocess me end |
.from_csv(from = [], sensitive = false) ⇒ Object
Create a new instance of the object from comma-delimited data. from
a JSON string split into an array by line.
440 441 442 |
# File 'lib/storable.rb', line 440 def self.from_csv(from=[], sensitive=false) self.from_delimited(from, ',', sensitive) end |
.from_delimited(from = [], delim = ',', sensitive = false) ⇒ 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.
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'lib/storable.rb', line 447 def self.from_delimited(from=[],delim=',',sensitive=false) return if from.empty? from = from.split($/) if String === from hash = {} fnames = sensitive ? (field_names-sensitive_fields) : field_names values = from[0].chomp.split(delim) 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.
197 198 199 200 201 202 203 204 |
# File 'lib/storable.rb', line 197 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.
215 216 217 218 219 220 221 222 |
# File 'lib/storable.rb', line 215 def self.from_hash(from={}) return nil if !from || from.empty? if self == Storable Storable::Anonymous.new from else new.from_hash(from) end 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).
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/storable.rb', line 390 def self.from_json(*from) from_str = [from].flatten.compact.join('') #from_str.force_encoding("ISO-8859-1") #p [:from, from_str.encoding.name] if from_str.respond_to?(:encoding) if YAJL_LOADED tmp = Yajl::Parser.parse(from_str, :check_utf8 => false) elsif JSON_LOADED tmp = JSON::load(from_str) else raise "JSON parser not loaded" end 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 = [], sensitive = false) ⇒ Object
Create a new instance from tab-delimited data.
from
a JSON string split into an array by line.
435 436 437 |
# File 'lib/storable.rb', line 435 def self.from_tsv(from=[], sensitive=false) self.from_delimited(from, "\t", sensitive) end |
.from_yaml(*from) ⇒ Object
Create a new instance of the object from YAML. from
a YAML String or Array (split into by line).
381 382 383 384 385 386 |
# File 'lib/storable.rb', line 381 def self.from_yaml(*from) from_str = [from].flatten.compact.join('') hash = YAML::load(from_str) hash = from_hash(hash) if Hash === hash hash end |
.has_field?(n) ⇒ Boolean
140 141 142 |
# File 'lib/storable.rb', line 140 def self.has_field?(n) field_names.member? n.to_sym end |
.inherited(obj) ⇒ Object
Passes along fields to inherited classes
49 50 51 52 53 54 55 |
# File 'lib/storable.rb', line 49 def self.inherited(obj) unless Storable == self obj.sensitive_fields = self.sensitive_fields.clone if !self.sensitive_fields.nil? obj.field_names = self.field_names.clone if !self.field_names.nil? obj.field_types = self.field_types.clone if !self.field_types.nil? end end |
.read_file_to_array(path) ⇒ Object
463 464 465 466 467 468 469 470 471 472 |
# File 'lib/storable.rb', line 463 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 |
.sensitive_field?(name) ⇒ Boolean
135 136 137 138 |
# File 'lib/storable.rb', line 135 def self.sensitive_field?(name) @sensitive_fields ||= [] @sensitive_fields.member?(name) end |
.write_file(path, content, flush = true) ⇒ Object
474 475 476 |
# File 'lib/storable.rb', line 474 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
482 483 484 485 486 487 488 489 490 491 |
# File 'lib/storable.rb', line 482 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
#call(fname) ⇒ Object
248 249 250 251 252 253 254 |
# File 'lib/storable.rb', line 248 def call(fname) unless field_types[fname.to_sym] == Proc && Proc === self.send(fname) raise "Field #{fname} is not a Proc" end self.instance_eval &self.send(fname) end |
#dump(format = nil, with_titles = false) ⇒ Object
Dump the object data to the given format.
184 185 186 187 188 189 |
# File 'lib/storable.rb', line 184 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}") end |
#field_names ⇒ Object
Returns an array of field names defined by self.field
171 172 173 |
# File 'lib/storable.rb', line 171 def field_names self.class.field_names #|| self.class.ancestors.first.field_names end |
#field_types ⇒ Object
Returns an array of field types defined by self.field. Fields that did not receive a type are set to nil.
176 177 178 |
# File 'lib/storable.rb', line 176 def field_types self.class.field_types #|| self.class.ancestors.first.field_types end |
#from_array(*from) ⇒ Object
232 233 234 235 236 237 |
# File 'lib/storable.rb', line 232 def from_array *from (self.field_names || []).each_with_index do |n,index| break if index >= from.size send("#{n}=", from[index]) end end |
#from_hash(from = {}) ⇒ Object
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/storable.rb', line 256 def from_hash(from={}) fnames = field_names return from if fnames.nil? || fnames.empty? fnames.each_with_index do |fname,index| ftype = field_types[fname] value_orig = from[fname.to_s] || from[fname.to_s.to_sym] next if value_orig.nil? if ( ftype == String or ftype == Symbol ) && value_orig.to_s.empty? value = '' elsif ftype == Array value = Array === value_orig ? value_orig : [value_orig] elsif ftype == Hash value = value_orig elsif !ftype.nil? value_orig = value_orig.first if Array === value_orig && value_orig.size == 1 if [Time, DateTime].member?(ftype) value = ftype.parse(value_orig) elsif [TrueClass, FalseClass, Boolean].member?(ftype) value = (value_orig.to_s.upcase == "TRUE") elsif ftype == Float value = value_orig.to_f elsif ftype == Integer value = value_orig.to_i elsif ftype == Symbol value = value_orig.to_s.to_sym elsif ftype == Range if Range === value_orig value = value_orig elsif Numeric === value_orig value = value_orig..value_orig else value_orig = value_orig.to_s if value_orig.match(/\.\.\./) el = value_orig.split('...') value = el.first.to_f...el.last.to_f elsif value_orig.match(/\.\./) el = value_orig.split('..') value = el.first.to_f..el.last.to_f else value = value_orig..value_orig end end elsif ftype == Proc && String === value_orig value = Proc.from_string value_orig end end value = value_orig if value.nil? if self.respond_to?("#{fname}=") self.send("#{fname}=", value) else self.instance_variable_set("@#{fname}", value) end end self.postprocess self end |
#has_field?(n) ⇒ Boolean
143 144 145 |
# File 'lib/storable.rb', line 143 def has_field?(n) self.class.field_names.member? n.to_sym end |
#has_processor?(fname) ⇒ Boolean
375 376 377 |
# File 'lib/storable.rb', line 375 def has_processor?(fname) self.respond_to? :"_storable_processor_#{fname}" end |
#init(*args) ⇒ Object
224 225 226 |
# File 'lib/storable.rb', line 224 def init *args from_array *args end |
#postprocess ⇒ Object
159 160 |
# File 'lib/storable.rb', line 159 def postprocess end |
#process(fname, val) ⇒ Object
371 372 373 |
# File 'lib/storable.rb', line 371 def process(fname, val) self.send :"_storable_processor_#{fname}", val end |
#sensitive! ⇒ Object
166 167 168 |
# File 'lib/storable.rb', line 166 def sensitive! @storable_sensitive = true end |
#sensitive? ⇒ Boolean
162 163 164 |
# File 'lib/storable.rb', line 162 def sensitive? @storable_sensitive == true end |
#sensitive_fields ⇒ Object
179 180 181 |
# File 'lib/storable.rb', line 179 def sensitive_fields self.class.sensitive_fields #|| self.class.ancestors.first.sensitive_fields end |
#to_array ⇒ Object
339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/storable.rb', line 339 def to_array preprocess if respond_to? :preprocess fields = sensitive? ? (field_names-sensitive_fields) : field_names fields.collect do |fname| next if sensitive? && self.class.sensitive_field?(fname) v = self.send(fname) v = process(fname, v) if has_processor?(fname) if Array === v v = v.collect { |v2| v2.kind_of?(Storable) ? v2.to_a : v2 } end v end 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)
430 431 432 |
# File 'lib/storable.rb', line 430 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.
412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/storable.rb', line 412 def to_delimited(with_titles=false, delim=',') preprocess if respond_to? :preprocess values = [] fields = sensitive? ? (field_names-sensitive_fields) : field_names fields.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.
206 207 208 209 210 211 212 |
# File 'lib/storable.rb', line 206 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 ⇒ Object
Return the object data as a hash with_titles
is ignored.
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/storable.rb', line 322 def to_hash preprocess if respond_to? :preprocess tmp = USE_ORDERED_HASH ? Storable::OrderedHash.new : {} if field_names field_names.each do |fname| next if sensitive? && self.class.sensitive_field?(fname) v = self.send(fname) v = process(fname, v) if has_processor?(fname) if Array === v v = v.collect { |v2| v2.kind_of?(Storable) ? v2.to_hash : v2 } end tmp[fname] = v.kind_of?(Storable) ? v.to_hash : v end end tmp end |
#to_json(*from, &blk) ⇒ Object
353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/storable.rb', line 353 def to_json(*from, &blk) preprocess if respond_to? :preprocess hash = to_hash if YAJL_LOADED # set by Storable ret = Yajl::Encoder.encode(hash) ret elsif JSON_LOADED JSON.generate(hash, *from, &blk) else raise "no JSON parser loaded" end end |
#to_string(*args) ⇒ Object
191 192 193 194 |
# File 'lib/storable.rb', line 191 def to_string(*args) # TODO: sensitive? 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)
425 426 427 |
# File 'lib/storable.rb', line 425 def to_tsv(with_titles=false) to_delimited(with_titles, "\t") end |
#to_yaml(*from, &blk) ⇒ Object
366 367 368 369 |
# File 'lib/storable.rb', line 366 def to_yaml(*from, &blk) preprocess if respond_to? :preprocess to_hash.to_yaml(*from, &blk) end |