class OpenStruct
OpenStruct
是一种数据结构,类似于哈希表,允许定义任意属性及其对应的值。这是通过使用 Ruby 的元编程在类本身上定义方法来实现的。
示例¶ ↑
require "ostruct" person = OpenStruct.new person.name = "John Smith" person.age = 70 person.name # => "John Smith" person.age # => 70 person.address # => nil
OpenStruct
内部使用哈希表来存储属性和值,甚至可以用哈希表进行初始化
australia = OpenStruct.new(:country => "Australia", :capital => "Canberra") # => #<OpenStruct country="Australia", capital="Canberra">
带有空格或通常不能用于方法调用的字符(例如()[]*
)的哈希键将不会立即在 OpenStruct
对象上作为方法用于检索或赋值,但仍然可以通过 Object#send 方法或使用 [] 来访问。
measurements = OpenStruct.new("length (in inches)" => 24) measurements[:"length (in inches)"] # => 24 measurements.send("length (in inches)") # => 24 message = OpenStruct.new(:queued? => true) message.queued? # => true message.send("queued?=", false) message.queued? # => false
删除属性的存在需要执行 delete_field
方法,因为将属性值设置为 nil
不会删除该属性。
first_pet = OpenStruct.new(:name => "Rowdy", :owner => "John Smith") second_pet = OpenStruct.new(:name => "Rowdy") first_pet.owner = nil first_pet # => #<OpenStruct name="Rowdy", owner=nil> first_pet == second_pet # => false first_pet.delete_field(:owner) first_pet # => #<OpenStruct name="Rowdy"> first_pet == second_pet # => true
Ractor 兼容性:一个具有可共享值的冻结的 OpenStruct
本身是可共享的。
注意事项¶ ↑
OpenStruct
利用 Ruby 的方法查找结构来查找和定义属性的必要方法。这是通过 method_missing 和 define_singleton_method 方法实现的。
如果担心创建的对象的性能,这应该是一个考虑因素,因为与使用哈希表或结构体相比,设置这些属性的开销要大得多。从小型哈希表创建 open struct 并访问其中的几个条目可能比直接访问哈希表慢 200 倍。
这是一个潜在的安全问题;从不受信任的用户数据(例如 JSON Web 请求)构建 OpenStruct
可能会受到“符号拒绝服务”攻击的影响,因为键会创建方法,并且方法的名称永远不会被垃圾回收。
这也可能是 Ruby 版本之间不兼容的根源
o = OpenStruct.new o.then # => nil in Ruby < 2.6, enumerator for Ruby >= 2.6
内置方法可能会以这种方式被覆盖,这可能是错误或安全问题的根源
o = OpenStruct.new o.methods # => [:to_h, :marshal_load, :marshal_dump, :each_pair, ... o.methods = [:foo, :bar] o.methods # => [:foo, :bar]
为了帮助解决冲突,OpenStruct
仅使用以 !
结尾的 protected/private 方法,并通过添加 !
为内置的公共方法定义别名
o = OpenStruct.new(make: 'Bentley', class: :luxury) o.class # => :luxury o.class! # => OpenStruct
建议(但不是强制)不要使用以 !
结尾的字段;请注意,子类的方法可能不会被覆盖,OpenStruct 自身以 !
结尾的方法也不会被覆盖。
出于所有这些原因,请考虑完全不使用 OpenStruct
。
常量
- HAS_PERFORMANCE_WARNINGS
- VERSION
公共类方法
创建一个新的 OpenStruct
对象。默认情况下,生成的 OpenStruct
对象将没有属性。
如果提供了可选的 hash
,它将生成属性和值(可以是哈希表、OpenStruct
或结构体)。例如
require "ostruct" hash = { "country" => "Australia", :capital => "Canberra" } data = OpenStruct.new(hash) data # => #<OpenStruct country="Australia", capital="Canberra">
# File ostruct.rb, line 134 def initialize(hash=nil) if HAS_PERFORMANCE_WARNINGS && Warning[:performance] warn "OpenStruct use is discouraged for performance reasons", uplevel: 1, category: :performance end if hash update_to_values!(hash) else @table = {} end end
公共实例方法
比较此对象和 other
是否相等。当 other
是 OpenStruct
且两个对象的哈希表相等时,OpenStruct
等于 other
。
require "ostruct" first_pet = OpenStruct.new("name" => "Rowdy") second_pet = OpenStruct.new(:name => "Rowdy") third_pet = OpenStruct.new("name" => "Rowdy", :age => nil) first_pet == second_pet # => true first_pet == third_pet # => false
# File ostruct.rb, line 423 def ==(other) return false unless other.kind_of?(OpenStruct) @table == other.table! end
返回属性的值,如果没有该属性,则返回 nil
。
require "ostruct" person = OpenStruct.new("name" => "John Smith", "age" => 70) person[:age] # => 70, same as person.age
# File ostruct.rb, line 303 def [](name) @table[name.to_sym] end
设置属性的值。
require "ostruct" person = OpenStruct.new("name" => "John Smith", "age" => 70) person[:age] = 42 # equivalent to person.age = 42 person.age # => 42
# File ostruct.rb, line 318 def []=(name, value) name = name.to_sym new_ostruct_member!(name) @table[name] = value end
从对象中删除指定的字段,并返回该字段如果已定义时所包含的值。您可以选择提供一个代码块。如果未定义该字段,则返回代码块的结果,如果未提供代码块,则引发 NameError。
require "ostruct" person = OpenStruct.new(name: "John", age: 70, pension: 300) person.delete_field!("age") # => 70 person # => #<OpenStruct name="John", pension=300>
将值设置为 nil
不会删除属性
person.pension = nil person # => #<OpenStruct name="John", pension=nil> person.delete_field('number') # => NameError person.delete_field('number') { 8675_309 } # => 8675309
# File ostruct.rb, line 371 def delete_field(name, &block) sym = name.to_sym begin singleton_class.remove_method(sym, "#{sym}=") rescue NameError end @table.delete(sym) do return yield if block raise! NameError.new("no field '#{sym}' in #{self}", sym) end end
查找并返回嵌套对象中由 name
和 identifiers
指定的对象。嵌套对象可能是各种类的实例。请参阅 Dig 方法。
示例
require "ostruct" address = OpenStruct.new("city" => "Anytown NC", "zip" => 12345) person = OpenStruct.new("name" => "John Smith", "address" => address) person.dig(:address, "zip") # => 12345 person.dig(:business_address, "zip") # => nil
# File ostruct.rb, line 340 def dig(name, *names) begin name = name.to_sym rescue NoMethodError raise! TypeError, "#{name} is not a symbol nor a string" end @table.dig(name, *names) end
产生所有属性(作为符号)以及相应的值,如果没有提供代码块,则返回一个枚举器。
require "ostruct" data = OpenStruct.new("country" => "Australia", :capital => "Canberra") data.each_pair.to_a # => [[:country, "Australia"], [:capital, "Canberra"]]
# File ostruct.rb, line 211 def each_pair return to_enum(__method__) { @table.size } unless defined?(yield) @table.each_pair{|p| yield p} self end
比较此对象和 other
是否相等。当 other
是 OpenStruct
且两个对象的哈希表 eql? 相等时,OpenStruct
与 other
eql? 相等。
# File ostruct.rb, line 433 def eql?(other) return false unless other.kind_of?(OpenStruct) @table.eql?(other.table!) end
# File ostruct.rb, line 269 def freeze @table.freeze super end
返回包含键和值的详细摘要的字符串。
# File ostruct.rb, line 388 def inspect ids = (Thread.current[InspectKey] ||= []) if ids.include?(object_id) detail = ' ...' else ids << object_id begin detail = @table.map do |key, value| " #{key}=#{value.inspect}" end.join(',') ensure ids.pop end end ['#<', self.class!, detail, '>'].join end
# File ostruct.rb, line 182 def to_h(&block) if block @table.to_h(&block) else @table.dup end end