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.10.0"
- 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.
227 228 229 |
# File 'lib/storable.rb', line 227 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.
149 150 151 |
# File 'lib/storable.rb', line 149 def format @format end |
Class Method Details
.append_file(path, content, flush = true) ⇒ Object
477 478 479 |
# File 'lib/storable.rb', line 477 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 |
# File 'lib/storable.rb', line 69 def self.field(*args, &processor) 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
238 239 240 241 242 243 244 245 |
# File 'lib/storable.rb', line 238 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.
439 440 441 |
# File 'lib/storable.rb', line 439 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.
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 |
# File 'lib/storable.rb', line 446 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.
196 197 198 199 200 201 202 203 |
# File 'lib/storable.rb', line 196 def self.from_file(file_path, format='yaml') raise "Cannot read file (#{file_path})" unless File.exist?(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.
214 215 216 217 218 219 220 221 |
# File 'lib/storable.rb', line 214 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).
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
# File 'lib/storable.rb', line 389 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.
434 435 436 |
# File 'lib/storable.rb', line 434 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).
380 381 382 383 384 385 |
# File 'lib/storable.rb', line 380 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
139 140 141 |
# File 'lib/storable.rb', line 139 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
462 463 464 465 466 467 468 469 470 471 |
# File 'lib/storable.rb', line 462 def self.read_file_to_array(path) contents = [] return contents unless File.exist?(path) open(path, 'r') do |l| contents = l.readlines end contents end |
.sensitive_field?(name) ⇒ Boolean
134 135 136 137 |
# File 'lib/storable.rb', line 134 def self.sensitive_field?(name) @sensitive_fields ||= [] @sensitive_fields.member?(name) end |
.write_file(path, content, flush = true) ⇒ Object
473 474 475 |
# File 'lib/storable.rb', line 473 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
481 482 483 484 485 486 487 488 489 490 |
# File 'lib/storable.rb', line 481 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
247 248 249 250 251 252 253 |
# File 'lib/storable.rb', line 247 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.
183 184 185 186 187 188 |
# File 'lib/storable.rb', line 183 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
170 171 172 |
# File 'lib/storable.rb', line 170 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.
175 176 177 |
# File 'lib/storable.rb', line 175 def field_types self.class.field_types #|| self.class.ancestors.first.field_types end |
#from_array(*from) ⇒ Object
231 232 233 234 235 236 |
# File 'lib/storable.rb', line 231 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
255 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 |
# File 'lib/storable.rb', line 255 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.new { "Procs can not be rehydrated as of v0.10" } 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
142 143 144 |
# File 'lib/storable.rb', line 142 def has_field?(n) self.class.field_names.member? n.to_sym end |
#has_processor?(fname) ⇒ Boolean
374 375 376 |
# File 'lib/storable.rb', line 374 def has_processor?(fname) self.respond_to? :"_storable_processor_#{fname}" end |
#init(*args) ⇒ Object
223 224 225 |
# File 'lib/storable.rb', line 223 def init *args from_array *args end |
#postprocess ⇒ Object
158 159 |
# File 'lib/storable.rb', line 158 def postprocess end |
#process(fname, val) ⇒ Object
370 371 372 |
# File 'lib/storable.rb', line 370 def process(fname, val) self.send :"_storable_processor_#{fname}", val end |
#sensitive! ⇒ Object
165 166 167 |
# File 'lib/storable.rb', line 165 def sensitive! @storable_sensitive = true end |
#sensitive? ⇒ Boolean
161 162 163 |
# File 'lib/storable.rb', line 161 def sensitive? @storable_sensitive == true end |
#sensitive_fields ⇒ Object
178 179 180 |
# File 'lib/storable.rb', line 178 def sensitive_fields self.class.sensitive_fields #|| self.class.ancestors.first.sensitive_fields end |
#to_array ⇒ Object
338 339 340 341 342 343 344 345 346 347 348 349 350 |
# File 'lib/storable.rb', line 338 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)
429 430 431 |
# File 'lib/storable.rb', line 429 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.
411 412 413 414 415 416 417 418 419 420 421 |
# File 'lib/storable.rb', line 411 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.
205 206 207 208 209 210 211 |
# File 'lib/storable.rb', line 205 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.
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/storable.rb', line 321 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
352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'lib/storable.rb', line 352 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
190 191 192 193 |
# File 'lib/storable.rb', line 190 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)
424 425 426 |
# File 'lib/storable.rb', line 424 def to_tsv(with_titles=false) to_delimited(with_titles, "\t") end |
#to_yaml(*from, &blk) ⇒ Object
365 366 367 368 |
# File 'lib/storable.rb', line 365 def to_yaml(*from, &blk) preprocess if respond_to? :preprocess to_hash.to_yaml(*from, &blk) end |