класс OpenStruct
OpenStruct — это структура данных, похожая на Hash, которая позволяет определять произвольные атрибуты вместе с соответствующими значениями. Это достигается с помощью метапрограммирования Ruby для определения методов непосредственно в классе.
Примеры
require "ostruct" person = OpenStruct.new person.name = "John Smith" person.age = 70 person.name # => "John Smith" person.age # => 70 person.address # => nil
OpenStruct использует внутри Hash для хранения атрибутов и значений и может быть даже инициализирован с его помощью:
australia = OpenStruct.new(:country => "Australia", :capital => "Canberra") # => #<OpenStruct country="Australia", capital="Canberra">
Ключи Hash с пробелами или символами, которые обычно нельзя использовать при вызове методов (например, ()[]*), не будут сразу доступны в качестве методов объекта 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.
Это следует учитывать, если вас беспокоит производительность создаваемых объектов, поскольку установка этих свойств связана со значительно большими накладными расходами по сравнению с использованием Hash или Struct. Создание открытой структуры из небольшого Hash и доступ к нескольким его элементам могут быть в 200 раз медленнее, чем прямой доступ к хешу.
Это может представлять угрозу безопасности: создание OpenStruct из недоверенных пользовательских данных (например, веб-запроса JSON) может сделать приложение уязвимым для атаки типа «отказ в обслуживании символами», поскольку ключи создают методы, а имена методов никогда не удаляются сборщиком мусора.
Это также может стать причиной несовместимости между версиями 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 использует только защищённые/приватные методы, заканчивающиеся на !, и определяет псевдонимы для встроенных публичных методов, добавляя !:
o = OpenStruct.new(make: 'Bentley', class: :luxury) o.class # => :luxury o.class! # => OpenStruct
Рекомендуется (но это не запрещено) не использовать поля, заканчивающиеся на !. Обратите внимание, что методы подкласса не могут быть переопределены, как и собственные методы OpenStruct, заканчивающиеся на !.
По всем этим причинам рекомендуется вообще не использовать OpenStruct.
Константы
- HAS_PERFORMANCE_WARNINGS
- VERSION
Публичные методы класса
# File ext/json/lib/json/add/ostruct.rb, line 13 def self.json_create(object) new(object['t'] || object[:t]) end
См. as_json.
# File lib/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 Создаёт новый объект OpenStruct. По умолчанию полученный объект OpenStruct не будет иметь атрибутов.
Необязательный аргумент hash, если он задан, создаёт атрибуты и значения (это может быть Hash, OpenStruct или Struct). Например:
require "ostruct"
hash = { "country" => "Australia", :capital => "Canberra" }
data = OpenStruct.new(hash)
data # => #<OpenStruct country="Australia", capital="Canberra">
Публичные методы экземпляра
# File lib/ostruct.rb, line 423 def ==(other) return false unless other.kind_of?(OpenStruct) @table == other.table! end
Сравнивает этот объект с other на равенство. Объект OpenStruct равен other, если other является объектом OpenStruct и таблицы Hash этих двух объектов равны.
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 lib/ostruct.rb, line 303 def [](name) @table[name.to_sym] end
Возвращает значение атрибута или nil, если такого атрибута нет.
require "ostruct"
person = OpenStruct.new("name" => "John Smith", "age" => 70)
person[:age] # => 70, same as person.age
# File lib/ostruct.rb, line 318 def []=(name, value) name = name.to_sym new_ostruct_member!(name) @table[name] = value end
Устанавливает значение атрибута.
require "ostruct"
person = OpenStruct.new("name" => "John Smith", "age" => 70)
person[:age] = 42 # equivalent to person.age = 42
person.age # => 42
# File ext/json/lib/json/add/ostruct.rb, line 33
def as_json(*)
klass = self.class.name
klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!"
{
JSON.create_id => klass,
't' => table,
}
end Методы OpenStruct#as_json и OpenStruct.json_create можно использовать для сериализации и десериализации объекта OpenStruct; см. Marshal.
Метод OpenStruct#as_json сериализует self, возвращая хеш из двух элементов, представляющий self:
require 'json/add/ostruct'
x = OpenStruct.new('name' => 'Rowdy', :age => nil).as_json
# => {"json_class"=>"OpenStruct", "t"=>{:name=>'Rowdy', :age=>nil}}
Метод JSON.create десериализует такой хеш и возвращает объект OpenStruct:
OpenStruct.json_create(x) # => #<OpenStruct name='Rowdy', age=nil>
# File lib/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 Удаляет из объекта поле с указанным именем и возвращает значение, которое содержало это поле, если оно было определено. Можно также передать блок. Если поле не определено, возвращается результат выполнения блока либо вызывается исключение 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 lib/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 Находит и возвращает объект во вложенных объектах, указанный аргументами 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 lib/ostruct.rb, line 211
def each_pair
return to_enum(__method__) { @table.size } unless defined?(yield)
@table.each_pair{|p| yield p}
self
end Передаёт в блок все атрибуты (в виде символов) вместе с соответствующими значениями или возвращает перечислитель, если блок не задан.
require "ostruct"
data = OpenStruct.new("country" => "Australia", :capital => "Canberra")
data.each_pair.to_a # => [[:country, "Australia"], [:capital, "Canberra"]]
# File lib/ostruct.rb, line 433 def eql?(other) return false unless other.kind_of?(OpenStruct) @table.eql?(other.table!) end
Сравнивает этот объект с other на равенство. Объект OpenStruct считается eql? с other, если other является объектом OpenStruct и таблицы Hash этих двух объектов равны по eql?.
# File lib/ostruct.rb, line 269 def freeze @table.freeze super end
Object#freeze # File lib/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 lib/ostruct.rb, line 182
def to_h(&block)
if block
@table.to_h(&block)
else
@table.dup
end
end # File ext/json/lib/json/add/ostruct.rb, line 51 def to_json(*args) as_json.to_json(*args) end
Возвращает строку JSON, представляющую self:
require 'json/add/ostruct'
puts OpenStruct.new('name' => 'Rowdy', :age => nil).to_json
Результат:
{"json_class":"OpenStruct","t":{'name':'Rowdy',"age":null}}
Приватные методы экземпляра
Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.