Class: SuperStruct
Overview
:title: SuperStruct
This is an easy way to create Struct-like classes; it converts easily between hashes and arrays, and it allows OpenStruct-like dynamic naming of members.
Unlike Struct, it creates a “real” class, and it has real instance variables with predictable names.
A basic limitation is that the hash keys must be legal method names (unless used with send()).
Basically, ss, ss, ss, and ss.alpha all mean the same.
What It’s Like
It’s like a Struct…
- you can pass in a list of symbols for accessors
- it will create a class for you
but…
- you don't have to pass in the class name
- it returns a "real" class
. instance variables have the expected names
. you can reopen and add methods
- it doesn't go into the Struct:: namespace
- it preserves the order of the fields
- you can use Strings instead of Symbols for the names
It’s like an Array…
- you can access the items by [number] and [number]=
but…
- you can also access the items by ["name"] and ["name"]=
- you can access the items by accessors
It’s like an OpenStruct…
- (if you use .open instead of .new) you can add fields
automatically with x.field or x.field=val
but…
- you can initialize it like a Struct
- it preserves the order of the fields
It’s like a Hash…
- data can be accessed by ["name"]
but…
- order (of entry or creation) is preserved
- arbitrary objects are not allowed (it does obj.to_str or obj.to_s)
- strings must be valid method names
It’s like Ara Howard’s Named Array…
- we can access elements by ["name"] or ["name"]=
but…
- you can access the items by accessors
- strings must be valid method names
It’s like Florian Gross’s Keyed List…
(to be done)
but…
- it preserves the order of the fields
Usage
# Need not assign to existing fields (default to nil)
myStruct = SuperStruct.new(:alpha)
x = myStruct.new
x.alpha # nil
# A value assigned at construction may be retrieved
myStruct = SuperStruct.new(:alpha)
x = myStruct.new(234)
x.alpha # 234
# Unassigned fields are nil
myStruct = SuperStruct.new(:alpha,:beta)
x = myStruct.new(234)
x.beta # nil
# An open structure may not construct with nonexistent fields
myStruct = SuperStruct.open
x = myStruct.new(234) # error
# An open structure may assign fields not previously existing
myStruct = SuperStruct.open
x = myStruct.new
x.foo = 123
x. = 456
# The act of retrieving a nonexistent field from an open struct will
# create that field
myStruct = SuperStruct.open
x = myStruct.new
x.foo # nil
# A field (in an open struct) that is unassigned will be nil
myStruct = SuperStruct.open
x = myStruct.new
y = x.
# A struct created with new rather than open cannot reference nonexistent
# fields
myStruct = SuperStruct.new
x = myStruct.new
x.foo # error
# Adding a field to a struct will create a writer and reader for that field
# An open struct will also create a writer and a reader together
# A field has a real writer and reader corresponding to it
# A string will work as well as a symbol
myStruct = SuperStruct.new("alpha")
# to_a will return an array of values
myStruct = SuperStruct.new("alpha","beta","gamma")
x = myStruct.new(7,8,9)
assert(x.to_a == [7,8,9])
# Instance method 'members' will return a list of members (as strings)
myStruct = SuperStruct.new(:alpha,"beta")
x = myStruct.new
assert_equal(["alpha","beta"],x.members)
# Class method 'members' will return a list of members (as strings)
myStruct = SuperStruct.new(:alpha,"beta")
assert_equal(["alpha","beta"],myStruct.members)
# to_ary will allow a struct to be treated like an array in
# multiple assignment
myStruct = SuperStruct.new("alpha","beta","gamma")
x = myStruct.new(7,8,9)
a,b,c = x
assert(b == 8)
# to_ary will allow a struct to be treated like an array in
# passed parameters
myStruct = SuperStruct.new("alpha","beta","gamma")
x = myStruct.new(7,8,9)
b = meth(*x)
# to_hash will return a hash with fields as keys
myStruct = SuperStruct.new("alpha","beta","gamma")
x = myStruct.new(7,8,9)
h = x.to_hash
assert_equal({"alpha"=>7,"beta"=>8,"gamma"=>9},h)
# A field name (String) may be used in a hash-like notation
myStruct = SuperStruct.new("alpha","beta","gamma")
x = myStruct.new(7,8,9)
y = x["beta"]
# A field name (Symbol) may be used in a hash-like notation
myStruct = SuperStruct.new("alpha","beta","gamma")
x = myStruct.new(7,8,9)
y = x[:beta]
# [offset,length] may be used as for arrays
myStruct = SuperStruct.new("alpha","beta","gamma")
x = myStruct.new(7,8,9)
y = x[0,2]
# Ranges may be used as for arrays
myStruct = SuperStruct.new("alpha","beta","gamma")
x = myStruct.new(7,8,9)
y = x[1..2]
# Adding a field to an open struct adds it to the instance
myStruct = SuperStruct.open(:alpha)
x = myStruct.new
x.beta = 5
# Adding a field to an open struct adds it to the class also
myStruct = SuperStruct.open(:alpha)
x = myStruct.new
x.beta = 5
# An array passed to SuperStruct.new need not be starred
myStruct = SuperStruct.new(%w[alpha beta gamma])
x = myStruct.new
# A hash passed to #set will set multiple values at once
myStruct = SuperStruct.new(%w[alpha beta gamma])
x = myStruct.new
hash = {"alpha"=>234,"beta"=>345,"gamma"=>456}
x.set(hash)
# ||= works properly
x = SuperStruct.open.new
x.foo ||= 333
x. = x. || 444
# attr_tester will create a ?-method
myStruct = SuperStruct.new(:alive)
myStruct.attr_tester :alive
x = myStruct.new(true)
x.alive? # true
Author(s)
-
Hal Fulton
Class Method Summary collapse
Class Method Details
.new(*args) ⇒ Object
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 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 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/mega/sstruct.rb', line 228 def SuperStruct.new(*args) @table = [] @setsyms = [] # Setter symbols klass = Class.new if (args.size == 1) && (args[0].is_a? Array) args = args[0] end strs = args.map {|x| x.to_s } args.each_with_index do |k,i| case when (! [String,Symbol].include? k.class) raise ArgumentError, "Need a String or Symbol" when (strs[i] !~ /[_a-zA-Z][_a-zA-Z0-9]*/) raise ArgumentError, "Illegal character" end k = k.intern if k.is_a? String @table << k @setsyms << (k.to_s + "=").intern klass.instance_eval { attr_accessor k } end setsyms = @setsyms table = @table vals = @vals klass.class_eval do attr_reader :singleton define_method(:initialize) do |*vals| n = vals.size m = table.size case when n < m # raise ArgumentError, "Too few arguments (#{n} for #{m})" # Never mind... extra variables will just be nil when n > m raise ArgumentError, "Too many arguments (#{n} for #{m})" end setsyms.each_with_index do |var,i| self.send(var,vals[i]) end end define_method(:pretty_print) do |q| # pp.rb support q.object_group(self) do q.seplist(self.members, proc { q.text "," }) do |member| # self.members.each do |member| # q.text "," # unless q.first? q.breakable q.text member.to_s q.text '=' q.group(1) do q.breakable '' q.pp self[member] end end end end define_method(:inspect) do str = "#<#{self.class}:" table.each {|item| str << " #{item}=#{self.send(item)}" } str + ">" end define_method(:[]) do |*index| case index.map {|x| x.class } when [Fixnum] self.send(table[*index]) when [Fixnum,Fixnum], [Range] table[*index].map {|x| self.send(x)} when [String] self.send(index[0].intern) when [Symbol] self.send(index[0]) else raise ArgumentError,"Illegal index" end end define_method(:[]=) do |*index| value = index[-1] index = index[0..-2] case index.map {|x| x.class } when [Fixnum] self.send(table[*index]) when [Fixnum,Fixnum], [Range] setsyms[*index].map {|x| self.send(x,value) } when [String] self.send(index[0].intern,value) when [Symbol] self.send(index[0],value) else raise ArgumentError,"Illegal index" end end define_method(:to_a) { table.map {|x| eval("@"+x.to_s) } } define_method(:to_ary) { to_a } define_method(:members) { table.map {|x| x.to_s } } define_method(:to_struct) do mems = table Struct.new("TEMP",*mems) # Struct::TEMP.new(*vals) # Why doesn't this work?? data = mems.map {|x| self.send(x) } Struct::TEMP.new(*data) end define_method(:to_hash) do hash = {} table.each do |mem| mem = mem.to_s hash.update(mem => self.send(mem)) end hash end define_method(:set) {|h| h.each_pair {|k,v| send(k.to_s+"=",v) } } # Class methods... @singleton = class << self self end @singleton.instance_eval do define_method(:members) do table.map {|x| x.to_s } end me = self define_method(:attr_tester) do |*syms| syms.each {|sym| alias_method(sym.to_s+"?",sym) } end end end klass end |
.open(*args) ⇒ Object
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'lib/mega/sstruct.rb', line 359 def SuperStruct.open(*args) klass = SuperStruct.new(*args) table = @table setsyms = @setsyms table = @table klass.class_eval do define_method(:method_missing) do |meth, *args| mname = meth.id2name if mname =~ /=$/ getter = mname.chop setter = mname elsif mname =~ /\?$/ raise NoMethodError # ?-methods are not created automatically else getter = mname setter = mname + "=" end gsym = getter.intern ssym = setter.intern ivar = "@" + getter setsyms << setter table << getter len = args.length if mname == getter klass.class_eval do # getter define_method(getter) do instance_variable_get(ivar) end end else klass.class_eval do # setter define_method(setter) do |*args| if len != 1 raise ArgumentError, "Wrong # of arguments (#{len} for 1)", caller(1) end instance_variable_set(ivar,args[0]) instance_variable_get(ivar) end end end if mname == setter self.send(setter,*args) else if len == 0 self.send(getter) else raise NoMethodError, "Undefined method '#{mname}' for #{self}", caller(1) end end end end klass end |