Class: Roby::ExtendedStruct
- Includes:
- DRbUndumped
- Defined in:
- lib/roby/state/state.rb
Overview
ExtendedStruct objects are OpenStructs where attributes have a default class. They are used to build hierarchical data structure on-the-fly
For instance root = ExtendedStruct.new root.child.value = 42
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 #name?. The second one will return true only if the attribute is defined and it is not false if (root.respond_to?(:child)
<do something>
end
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 ExtendedStruct, you will want YAML#y to not get in the way. The exceptions are the methods listed in NOT_OVERRIDABLE
Direct Known Subclasses
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.
-
#children_class ⇒ Object
readonly
Returns the value of attribute children_class.
Class Method Summary collapse
Instance Method Summary collapse
-
#__respond_to__(name) ⇒ Object
1.8.7’s #respond_to? takes two arguments, 1.8.6 only one.
- #_dump(lvl = -1)) ⇒ Object
- #alias(from, to) ⇒ Object
-
#attach ⇒ Object
:nodoc:.
- #attach_child(name, obj) ⇒ Object
- #clear ⇒ Object
- #delete(name = nil) ⇒ 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. - #get(name, default_value) ⇒ Object
-
#initialize(children_class = ExtendedStruct, attach_to = nil, attach_name = nil) ⇒ ExtendedStruct
constructor
attach_toandattach_nameare used so that root = ExtendedStruct.new root.bla does not add ablaattribute to root, while the following constructs root.bla.test = 20 bla = root.bla bla.test = 20 does. -
#method_missing(name, *args, &update) ⇒ Object
:nodoc:.
-
#on(name = nil, &block) ⇒ Object
Call
blockwith the new value ifnamechanges. -
#respond_to?(name) ⇒ Boolean
:nodoc:.
-
#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 ExtendedStruct 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) ⇒ Object
Constructor Details
#initialize(children_class = ExtendedStruct, attach_to = nil, attach_name = nil) ⇒ ExtendedStruct
attach_to and attach_name are used so that
root = ExtendedStruct.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
52 53 54 55 56 57 |
# File 'lib/roby/state/state.rb', line 52 def initialize(children_class = ExtendedStruct, attach_to = nil, attach_name = nil) # :nodoc clear @attach_as = [attach_to, attach_name.to_s] if attach_to @children_class = children_class @observers = Hash.new { |h, k| h[k] = [] } end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args, &update) ⇒ Object
:nodoc:
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 |
# File 'lib/roby/state/state.rb', line 265 def method_missing(name, *args, &update) # :nodoc: name = name.to_s super(name.to_sym, *args, &update) if name =~ FORBIDDEN_NAMES_RX if name =~ /(.+)=$/ # Setter name = $1 value = *args if stable? raise NoMethodError, "#{self} is stable" elsif @filters.has_key?(name) && !@filters[name].call(value) raise ArgumentError, "value #{value} is not valid for #{name}" elsif !@members.has_key?(name) && !@aliases.has_key?(name) && respond_to?(name) if NOT_OVERRIDABLE_RX =~ name raise ArgumentError, "#{name} is already defined an cannot be overriden" end # Override it singleton_class.class_eval { private name } end attach @aliases.delete(name) pending = @pending.delete(name) if pending && pending != value pending.detach end @members[name] = value updated(name, value) return value elsif name =~ /(.+)\?$/ # Test name = $1 respond_to?(name) && send(name) elsif args.empty? # getter attach 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)" else member = children_class.new(children_class, self, name) @pending[name] = member end end if update member.update(&update) else member end else super(name.to_sym, *args, &update) end end |
Instance Attribute Details
#__parent_name ⇒ Object (readonly)
Returns the value of attribute __parent_name.
100 101 102 |
# File 'lib/roby/state/state.rb', line 100 def __parent_name @__parent_name end |
#__parent_struct ⇒ Object (readonly)
Returns the value of attribute __parent_struct.
100 101 102 |
# File 'lib/roby/state/state.rb', line 100 def __parent_struct @__parent_struct end |
#children_class ⇒ Object (readonly)
Returns the value of attribute children_class.
98 99 100 |
# File 'lib/roby/state/state.rb', line 98 def children_class @children_class end |
Class Method Details
._load(io) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/roby/state/state.rb', line 68 def self._load(io) marshalled_members, aliases = Marshal.load(io) members = marshalled_members.inject({}) do |h, (n, mv)| begin h[n] = Marshal.load(mv) rescue Exception Roby::Distributed.warn "cannot load #{n} #{mv}: #{$!.message}" end h end result = ExtendedStruct.new result.instance_variable_set("@members", members) result.instance_variable_set("@aliases", aliases) result rescue Exception Roby::Distributed.warn "cannot load #{members} #{io}: #{$!.message}" raise end |
Instance Method Details
#__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)
236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/roby/state/state.rb', line 236 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 |
#_dump(lvl = -1)) ⇒ Object
90 91 92 93 94 95 96 |
# File 'lib/roby/state/state.rb', line 90 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
333 334 335 |
# File 'lib/roby/state/state.rb', line 333 def alias(from, to) @aliases[to.to_s] = from.to_s end |
#attach ⇒ Object
:nodoc:
101 102 103 104 105 106 107 |
# File 'lib/roby/state/state.rb', line 101 def attach # :nodoc: if @attach_as @__parent_struct, @__parent_name = @attach_as @attach_as = nil __parent_struct.attach_child(__parent_name, self) end end |
#attach_child(name, obj) ⇒ Object
111 112 113 |
# File 'lib/roby/state/state.rb', line 111 def attach_child(name, obj) @members[name.to_s] = obj end |
#clear ⇒ Object
59 60 61 62 63 64 65 66 |
# File 'lib/roby/state/state.rb', line 59 def clear @attach_as = nil @stable = false @members = Hash.new @pending = Hash.new @filters = Hash.new @aliases = Hash.new end |
#delete(name = nil) ⇒ Object
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 183 |
# File 'lib/roby/state/state.rb', line 157 def delete(name = nil) raise TypeError, "#{self} is stable" if stable? if name name = name.to_s if child = @members.delete(name) child.instance_variable_set(:@__parent_struct, nil) child.instance_variable_set(:@__parent_name, nil) elsif child = @pending.delete(name) child.instance_variable_set(:@attach_as, nil) elsif child = @aliases.delete(name) # nothing to do here else 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 |
#each_member(&block) ⇒ Object
Iterates on all defined members of this object
137 138 139 |
# File 'lib/roby/state/state.rb', line 137 def each_member(&block) @members.each(&block) end |
#empty? ⇒ Boolean
Returns true if this object has no member
218 |
# File 'lib/roby/state/state.rb', line 218 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, and should return true if the new value if valid or false otherwise
188 189 190 |
# File 'lib/roby/state/state.rb', line 188 def filter(name, &block) @filters[name.to_s] = block end |
#get(name, default_value) ⇒ Object
251 252 253 254 255 256 257 |
# File 'lib/roby/state/state.rb', line 251 def get(name, default_value) if respond_to?(name) send(name.to_sym) else default_value end end |
#on(name = nil, &block) ⇒ Object
Call block with the new value if name changes
117 118 119 120 |
# File 'lib/roby/state/state.rb', line 117 def on(name = nil, &block) name = name.to_s if name @observers[name] << block end |
#respond_to?(name) ⇒ Boolean
:nodoc:
221 222 223 224 |
# File 'lib/roby/state/state.rb', line 221 def respond_to?(name, include_private = false) # :nodoc: return true if super return __respond_to__(name) 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.
199 200 201 202 203 204 |
# File 'lib/roby/state/state.rb', line 199 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
194 |
# File 'lib/roby/state/state.rb', line 194 def stable?; @stable end |
#to_hash(recursive = true) ⇒ Object
Converts this ExtendedStruct 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
125 126 127 128 129 130 131 132 133 134 |
# File 'lib/roby/state/state.rb', line 125 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
150 151 152 153 154 155 |
# File 'lib/roby/state/state.rb', line 150 def update(hash = nil) attach hash.each { |k, v| send("#{k}=", v) } if hash yield(self) if block_given? self end |
#updated(name, value) ⇒ Object
206 207 208 209 210 211 212 213 214 215 |
# File 'lib/roby/state/state.rb', line 206 def updated(name, value) if @observers.has_key?(name) @observers[name].each { |b| b.call(value) } end @observers[nil].each { |b| b.call(value) } if __parent_struct __parent_struct.updated(__parent_name, self) end end |