Class: Roby::OpenStruct
Overview
This module defines functionality that can be mixed-in other objects to have an ‘automatically extensible struct’ behaviour, i.e.
Roby::OpenStruct objects are OpenStructs where attributes have a default class. They are used to build hierarchical data structure on-the-fly. Additionally, they may have a model which constrains what can be created on them
For instance
However, you cannot check if a value is defined or not with
if (root.child)
<do something>
end
You’ll have to test with respond_to? or field_name?. The second one will return true only if the attribute is defined and it is not false
Handling of methods defined on parents
Methods defined in Object or Kernel are automatically overriden if needed. For instance, if you’re managing a (x, y, z) position using OpenStruct, you will want YAML#y to not get in the way. The exceptions are the methods listed in NOT_OVERRIDABLE
Direct Known Subclasses
ConfModel, GoalSpace, OpenStructModel, StateDataSourceField, StateField, StateLastValueField
Defined Under Namespace
Classes: Observer
Constant Summary collapse
- FORBIDDEN_NAMES =
%w{marshal each enum to}.map { |str| "^#{str}_" }
- FORBIDDEN_NAMES_RX =
/(?:#{FORBIDDEN_NAMES.join("|")})/- NOT_OVERRIDABLE =
%w{class} + instance_methods(false)
- NOT_OVERRIDABLE_RX =
/(?:#{NOT_OVERRIDABLE.join("|")})/
Instance Attribute Summary collapse
-
#__parent_name ⇒ Object
readonly
Returns the value of attribute __parent_name.
-
#__parent_struct ⇒ Object
readonly
Returns the value of attribute __parent_struct.
-
#model ⇒ Object
readonly
Returns the value of attribute model.
Class Method Summary collapse
Instance Method Summary collapse
- #__get(name, create_substruct = true, &update) ⇒ Object
- #__merge(other) ⇒ Object
- #__parent ⇒ Object
-
#__respond_to__(name) ⇒ Object
1.8.7’s #respond_to? takes two arguments, 1.8.6 only one.
- #__root ⇒ Object
- #__root? ⇒ Boolean
- #_dump(lvl = -1)) ⇒ Object
- #alias(from, to) ⇒ Object
-
#attach ⇒ Object
When a field is dynamically created by #method_missing, it is created in a pending state, in which it is not yet attached to its parent structure.
-
#attach_child(name, obj) ⇒ Object
Called by a child when #attach is called.
-
#attach_model ⇒ Object
Do the necessary initialization after having added a model to this task.
- #attach_to(parent, name) ⇒ Object
-
#attached? ⇒ Boolean
If true, this field is attached to a parent structure.
- #clear ⇒ Object
- #clear_model ⇒ Object
- #create_model ⇒ Object
-
#create_subfield(name) ⇒ Object
Called by #method_missing to create a subfield when needed.
- #delete(name = nil) ⇒ Object
- #detached! ⇒ Object
-
#each_member(&block) ⇒ Object
Iterates on all defined members of this object.
-
#empty? ⇒ Boolean
Returns true if this object has no member.
-
#filter(name, &block) ⇒ Object
Define a filter for the
nameattribute on self. - #freeze ⇒ Object
-
#get(name) ⇒ Object
Returns the value of the given field.
-
#global_filter(&block) ⇒ Object
Define a filter for the
nameattribute on self. -
#has_method?(name) ⇒ Boolean
has_method? will be used to know if a given method is already defined on the OpenStruct object, without taking into account the members and aliases.
-
#initialize(model = nil, attach_to = nil, attach_name = nil) ⇒ OpenStruct
constructor
attach_toandattach_nameare used so that root = OpenStruct.new root.bla does not add ablaattribute to root, while the following constructs root.bla.test = 20 bla = root.bla bla.test = 20 does. - #link_to(parent, name) ⇒ Object
-
#method_missing(name, *args, &update) ⇒ Object
:nodoc:.
-
#new_model ⇒ Object
Create a model structure and associate it with this openstruct.
-
#on_change(name = nil, recursive = false, &block) ⇒ Object
Call
blockwith the new value ifnamechanges. -
#path ⇒ Object
Returns the path to root, i.e.
- #pretty_print(pp) ⇒ Object
-
#respond_to?(name) ⇒ Boolean
:nodoc:.
- #set(name, *args) ⇒ Object
-
#stable!(recursive = false, is_stable = true) ⇒ Object
Sets the stable attribute of
selftois_stable. -
#stable? ⇒ Boolean
If self is stable, it cannot be updated.
-
#to_hash(recursive = true) ⇒ Object
Converts this OpenStruct into a corresponding hash, where all keys are symbols.
-
#update(hash = nil) {|_self| ... } ⇒ Object
Update a set of values on this struct If a hash is given, it is an name => value hash of attribute values.
- #updated(name, value, recursive = false) ⇒ Object
Constructor Details
#initialize(model = nil, attach_to = nil, attach_name = nil) ⇒ OpenStruct
attach_to and attach_name are used so that
root = OpenStruct.new
root.bla
does not add a bla attribute to root, while the following constructs
root.bla.test = 20
bla = root.bla
bla.test = 20
does
Note, however that
bla = root.bla
root.bla = 10
bla.test = 20
will not make root.bla be the bla object. And that
bla = root.bla
root.stable!
bla.test = 20
will not fail
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/roby/state/open_struct.rb', line 65 def initialize(model = nil, attach_to = nil, attach_name = nil) # :nodoc clear @model = model @observers = Hash.new { |h, k| h[k] = [] } @filters = Hash.new if attach_to link_to(attach_to, attach_name) end if model attach_model attach end end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args, &update) ⇒ Object
:nodoc:
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 |
# File 'lib/roby/state/open_struct.rb', line 534 def method_missing(name, *args, &update) # :nodoc: if name !~ /^\w+(?:\?|=|!)?$/ if name[-1, 1] == '?' return false else super end end name = name.to_s if name =~ FORBIDDEN_NAMES_RX super(name.to_sym, *args, &update) end if name =~ /^(\w+)=$/ ret = set($1, *args) return ret elsif name =~ /^(\w+)\?$/ # Test name = @aliases[$1] || $1 respond_to?(name) && get(name) && send(name) elsif args.empty? # getter attach return __get(name, &update) else super(name.to_sym, *args, &update) end end |
Instance Attribute Details
#__parent_name ⇒ Object (readonly)
Returns the value of attribute __parent_name.
140 141 142 |
# File 'lib/roby/state/open_struct.rb', line 140 def __parent_name @__parent_name end |
#__parent_struct ⇒ Object (readonly)
Returns the value of attribute __parent_struct.
140 141 142 |
# File 'lib/roby/state/open_struct.rb', line 140 def __parent_struct @__parent_struct end |
#model ⇒ Object (readonly)
Returns the value of attribute model.
41 42 43 |
# File 'lib/roby/state/open_struct.rb', line 41 def model @model end |
Class Method Details
._load(io) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/roby/state/open_struct.rb', line 107 def self._load(io) marshalled_members, aliases = Marshal.load(io) result = new marshalled_members.each do |name, marshalled_field| begin value = Marshal.load(marshalled_field) if value.kind_of?(OpenStruct) value.attach_to(result, name) else result.set(name, value) end rescue Exception Roby::DRoby.warn "cannot load #{name} #{marshalled_field}: #{$!.message}" end end result.instance_variable_set("@aliases", aliases) result rescue Exception Roby::DRoby.warn "cannot load #{marshalled_members} #{io}: #{$!.message}" raise end |
Instance Method Details
#__get(name, create_substruct = true, &update) ⇒ Object
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 |
# File 'lib/roby/state/open_struct.rb', line 451 def __get(name, create_substruct = true, &update) name = name.to_s if model # We never automatically create levels as the model should tell us # what we want create_substruct = false end if @members.has_key?(name) member = @members[name] else if alias_to = @aliases[name] return send(alias_to) elsif stable? raise NoMethodError, "no such attribute #{name} (#{self} is stable)" elsif create_substruct attach member = @pending[name] = create_subfield(name) else return end end if update member.update(&update) else member end end |
#__merge(other) ⇒ Object
577 578 579 580 581 582 583 584 585 586 587 588 |
# File 'lib/roby/state/open_struct.rb', line 577 def __merge(other) @members.merge(other) do |k, v1, v2| if v1.kind_of?(OpenStruct) && v2.kind_of?(OpenStruct) if v1.class != v2.class raise ArgumentError, "#{k} is a #{v1.class} in self and #{v2.class} in other, I don't know what to do" end v1.__merge(v2) else v2 end end end |
#__parent ⇒ Object
220 221 222 223 |
# File 'lib/roby/state/open_struct.rb', line 220 def __parent @__parent_struct || (@attach_as[0] if @attach_as) end |
#__respond_to__(name) ⇒ Object
1.8.7’s #respond_to? takes two arguments, 1.8.6 only one. This is the common implementation for both version. #respond_to? is adapted (see above)
416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/roby/state/open_struct.rb', line 416 def __respond_to__(name) # :nodoc: name = name.to_s return false if name =~ FORBIDDEN_NAMES_RX if name =~ /=$/ !@stable else if @members.has_key?(name) true else (alias_to = @aliases[name]) && respond_to?(alias_to) end end end |
#__root ⇒ Object
225 226 227 228 229 230 |
# File 'lib/roby/state/open_struct.rb', line 225 def __root if p = __parent return p.__root else self end end |
#__root? ⇒ Boolean
216 217 218 |
# File 'lib/roby/state/open_struct.rb', line 216 def __root? !__parent end |
#_dump(lvl = -1)) ⇒ Object
132 133 134 135 136 137 138 |
# File 'lib/roby/state/open_struct.rb', line 132 def _dump(lvl = -1) marshalled_members = @members.map do |name, value| [name, Marshal.dump(value)] rescue nil end marshalled_members.compact! Marshal.dump([marshalled_members, @aliases]) end |
#alias(from, to) ⇒ Object
567 568 569 |
# File 'lib/roby/state/open_struct.rb', line 567 def alias(from, to) @aliases[to.to_s] = from.to_s end |
#attach ⇒ Object
When a field is dynamically created by #method_missing, it is created in a pending state, in which it is not yet attached to its parent structure
This method does the attachment. It calls #attach_child on the parent to notify it
189 190 191 192 193 194 195 196 197 198 |
# File 'lib/roby/state/open_struct.rb', line 189 def attach if @attach_as @__parent_struct, @__parent_name = @attach_as @attach_as = nil __parent_struct.attach_child(__parent_name, self) if @model @model.attach end end end |
#attach_child(name, obj) ⇒ Object
Called by a child when #attach is called
210 211 212 213 |
# File 'lib/roby/state/open_struct.rb', line 210 def attach_child(name, obj) @members[name.to_s] = obj updated(name, obj) end |
#attach_model ⇒ Object
Do the necessary initialization after having added a model to this task
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/roby/state/open_struct.rb', line 157 def attach_model model.each_member do |name, field| case field when OpenStructModel @members[name] ||= create_subfield(name) end end # Trigger updating the structure whenever the state model is # changed model.on_change(nil, false) do |name, value| if value.kind_of?(OpenStructModel) @members[name] ||= create_subfield(name) end end end |
#attach_to(parent, name) ⇒ Object
178 179 180 181 |
# File 'lib/roby/state/open_struct.rb', line 178 def attach_to(parent, name) link_to(parent, name) attach end |
#attached? ⇒ Boolean
If true, this field is attached to a parent structure
233 234 235 |
# File 'lib/roby/state/open_struct.rb', line 233 def attached? !!@__parent_struct end |
#clear ⇒ Object
82 83 84 85 86 87 88 |
# File 'lib/roby/state/open_struct.rb', line 82 def clear @attach_as = nil @stable = false @members = Hash.new @pending = Hash.new @aliases = Hash.new end |
#clear_model ⇒ Object
90 91 92 |
# File 'lib/roby/state/open_struct.rb', line 90 def clear_model @model = nil end |
#create_model ⇒ Object
151 152 153 |
# File 'lib/roby/state/open_struct.rb', line 151 def create_model OpenStructModel.new end |
#create_subfield(name) ⇒ Object
Called by #method_missing to create a subfield when needed.
The default is to create a subfield of the same class than self
484 485 486 487 |
# File 'lib/roby/state/open_struct.rb', line 484 def create_subfield(name) model = if self.model then self.model.get(name) end self.class.new(model, self, name) end |
#delete(name = nil) ⇒ Object
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 |
# File 'lib/roby/state/open_struct.rb', line 294 def delete(name = nil) raise TypeError, "#{self} is stable" if stable? if name name = name.to_s child = @members.delete(name) || @pending.delete(name) if child && child.respond_to?(:detached!) child.detached! end # We don't detach aliases if !child && !@aliases.delete(name) raise ArgumentError, "no such child #{name}" end # and remove aliases that point to +name+ @aliases.delete_if { |_, pointed_to| pointed_to == name } else if __parent_struct __parent_struct.delete(__parent_name) elsif @attach_as @attach_as.first.delete(@attach_as.last) else raise ArgumentError, "#{self} is attached to nothing" end end end |
#detached! ⇒ Object
322 323 324 |
# File 'lib/roby/state/open_struct.rb', line 322 def detached! @__parent_struct, @__parent_name, @attach_as = nil end |
#each_member(&block) ⇒ Object
Iterates on all defined members of this object
274 275 276 |
# File 'lib/roby/state/open_struct.rb', line 274 def each_member(&block) @members.each(&block) end |
#empty? ⇒ Boolean
Returns true if this object has no member
384 |
# File 'lib/roby/state/open_struct.rb', line 384 def empty?; @members.empty? end |
#filter(name, &block) ⇒ Object
Define a filter for the name attribute on self. The given block is called when the attribute is written with both the attribute name and value. It should return the value that should actually be written, and raise an exception if the new value is invalid.
330 331 332 |
# File 'lib/roby/state/open_struct.rb', line 330 def filter(name, &block) @filters[name.to_s] = block end |
#freeze ⇒ Object
346 347 348 349 350 351 |
# File 'lib/roby/state/open_struct.rb', line 346 def freeze freeze each_member do |name, field| field.freeze end end |
#get(name) ⇒ Object
Returns the value of the given field
Unlike #method_missing, it will return nil if the field is not set
434 435 436 |
# File 'lib/roby/state/open_struct.rb', line 434 def get(name) __get(name, false) end |
#global_filter(&block) ⇒ Object
Define a filter for the name attribute on self. The given block is called when the attribute is written with both the attribute name and value. It should return the value that should actually be written, and raise an exception if the new value is invalid.
338 339 340 |
# File 'lib/roby/state/open_struct.rb', line 338 def global_filter(&block) @filters[nil] = block end |
#has_method?(name) ⇒ Boolean
has_method? will be used to know if a given method is already defined on the OpenStruct object, without taking into account the members and aliases.
390 391 392 |
# File 'lib/roby/state/open_struct.rb', line 390 def has_method?(name) Object.instance_method(:respond_to?).bind(self).call(name, true) end |
#link_to(parent, name) ⇒ Object
174 175 176 |
# File 'lib/roby/state/open_struct.rb', line 174 def link_to(parent, name) @attach_as = [parent, name] end |
#new_model ⇒ Object
Create a model structure and associate it with this openstruct
143 144 145 146 147 148 149 |
# File 'lib/roby/state/open_struct.rb', line 143 def new_model if !@model @model = create_model attach_model end @model end |
#on_change(name = nil, recursive = false, &block) ⇒ Object
Call block with the new value if name changes
If name is not given, it will be called for any change
252 253 254 255 256 257 |
# File 'lib/roby/state/open_struct.rb', line 252 def on_change(name = nil, recursive = false, &block) attach name = name.to_s if name @observers[name] << Observer.new(recursive, block) self end |
#path ⇒ Object
Returns the path to root, i.e. the list of field names from the root of the extended struct tree
440 441 442 443 444 445 446 447 448 449 |
# File 'lib/roby/state/open_struct.rb', line 440 def path result = [] obj = self while obj result.unshift(obj.__parent_name) obj = obj.__parent_struct end result.shift # we alwas add a nil for one-after-the-root result end |
#pretty_print(pp) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/roby/state/open_struct.rb', line 94 def pretty_print(pp) pp.seplist(@members) do |child| child_name, child_obj = *child if child_obj.kind_of?(OpenStruct) pp.text "#{child_name} >" else pp.text "#{child_name}" end pp.breakable child_obj.pretty_print(pp) end end |
#respond_to?(name) ⇒ Boolean
:nodoc:
394 395 396 397 |
# File 'lib/roby/state/open_struct.rb', line 394 def respond_to?(name, include_private = false) # :nodoc: return true if super return __respond_to__(name) end |
#set(name, *args) ⇒ Object
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 |
# File 'lib/roby/state/open_struct.rb', line 489 def set(name, *args) name = name.to_s name = @aliases[name] || name if model && !model.get(name).kind_of?(OpenStructModel::Variable) raise ArgumentError, "#{name} is not a state variable on #{self}" end value = args.first if stable? raise NoMethodError, "#{self} is stable" elsif @filters.has_key?(name) value = @filters[name].call(value) elsif @filters.has_key?(nil) value = @filters[nil].call(name, value) end if has_method?(name) if NOT_OVERRIDABLE_RX =~ name raise ArgumentError, "#{name} is already defined an cannot be overriden" end # Override it singleton_class.class_eval do define_method(name) do method_missing(name) end end end attach @aliases.delete(name) pending = @pending.delete(name) if pending && pending != value pending.detach end @members[name] = value updated(name, value) return value end |
#stable!(recursive = false, is_stable = true) ⇒ Object
Sets the stable attribute of self to is_stable. If recursive is true, set it on the child struct as well.
356 357 358 359 360 361 |
# File 'lib/roby/state/open_struct.rb', line 356 def stable!(recursive = false, is_stable = true) @stable = is_stable if recursive @members.each { |name, object| object.stable!(recursive, is_stable) if object.respond_to?(:stable!) } end end |
#stable? ⇒ Boolean
If self is stable, it cannot be updated. That is, calling a setter method raises NoMethodError
344 |
# File 'lib/roby/state/open_struct.rb', line 344 def stable?; @stable end |
#to_hash(recursive = true) ⇒ Object
Converts this OpenStruct into a corresponding hash, where all keys are symbols. If recursive is true, any member which responds to #to_hash will be converted as well
262 263 264 265 266 267 268 269 270 271 |
# File 'lib/roby/state/open_struct.rb', line 262 def to_hash(recursive = true) result = Hash.new @members.each do |k, v| result[k.to_sym] = if recursive && v.respond_to?(:to_hash) v.to_hash else v end end result end |
#update(hash = nil) {|_self| ... } ⇒ Object
Update a set of values on this struct If a hash is given, it is an name => value hash of attribute values. A given block is yield with self, so that the construct
my.extendable.struct.very.deep.update do |deep|
<update deep>
end
can be used
287 288 289 290 291 292 |
# File 'lib/roby/state/open_struct.rb', line 287 def update(hash = nil) attach hash.each { |k, v| send("#{k}=", v) } if hash yield(self) if block_given? self end |
#updated(name, value, recursive = false) ⇒ Object
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 |
# File 'lib/roby/state/open_struct.rb', line 363 def updated(name, value, recursive = false) if @observers.has_key?(name) @observers[name].each do |ob| if ob.recursive? || !recursive ob.call(name, value) end end end @observers[nil].each do |ob| if ob.recursive? || !recursive ob.call(name, value) end end if __parent_struct __parent_struct.updated(__parent_name, self, true) end end |