класс 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. Создание структуры open из небольшого 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 10 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 30
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, возвращая хеш из 2 элементов, представляющий 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 равен other, когда other является объектом OpenStruct, а таблицы Hash двух объектов равны.
# 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 48 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–2022 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.