Как сохранить строковый идентификатор в атрибуте модели
Я использую Virtus для создания моделей, представляющих объекты Salesforce.
Я пытаюсь создать атрибуты с понятными именами, которые используются для доступа к значению и методу, которые я могу использовать для получения идентификатора "String" для этой переменной.
Object.attribute #=> "BOB"
Object.get_identifier(:attribute_name) #=> "KEY"
# OR something like this
Object.attribute.identifier #=> "KEY"
Дружественное имя используется как метод получения / установки и идентификатор, который я могу сохранить для каждого атрибута, соответствующего имени API.
Вот пример:
class Case
include Virtus.model
attribute :case_number, String, identifier: 'Case_Number__c'
end
c = Case.new(case_number: 'XXX')
c.case_number #=> 'XXX'
c.case_number.identifier #=> 'Case_Number__c'
Или вместо того, чтобы иметь метод для самого Атрибута, возможно, создается вторичный метод для каждого набора идентификаторов:
c.case_number #=> 'XXX'
c.case_number_identifier #=> 'Case_Number__c'
Могу ли я расширить Virtus::Attribute и добавить это? Если это так, я не уверен, как это сделать.
2 ответа
Обезьяна, исправляющая Виртуса Attribute
класс, конечно, вариант.
Однако проникновение во внутреннюю часть библиотеки делает вас уязвимыми к рефакторингу в частной части исходного кода этой библиотеки.
Вместо этого вы можете использовать вспомогательный модуль, который инкапсулирует эту функцию. Вот предложение как:
require 'virtus'
# Put this helper module somewhere on your load path (e.g. your project's lib directory)
module ApiThing
def self.included(base)
base.include Virtus.model
base.extend ApiThing::ClassMethods
end
module ClassMethods
@@identifiers = {}
def api_attribute(attr_name, *virtus_args, identifier:, **virtus_options)
attribute attr_name, *virtus_args, **virtus_options
@@identifiers[attr_name.to_sym] = identifier
end
def identifier_for(attr_name)
@@identifiers.fetch(attr_name.to_sym){ raise ArgumentError, "unknown API attribute #{attr_name.inspect}" }
end
end
def identifier_for(attr_name)
self.class.identifier_for(attr_name)
end
end
# And include it in your API classes
class Balls
include ApiThing
api_attribute :color, String, identifier: 'SOME__fancy_identifier'
api_attribute :number, Integer, identifier: 'SOME__other_identifier'
api_attribute :size, BigDecimal, identifier: 'THAT__third_identifier'
end
# The attributes will be registered with Virtus – as usual
puts Balls.attribute_set[:color].type #=> Axiom::Types::String
puts Balls.attribute_set[:number].type #=> Axiom::Types::Integer
puts Balls.attribute_set[:size].type #=> Axiom::Types::Decimal
# You can use the handy Virtus constructor that takes a hash – as usual
b = Balls.new(color: 'red', number: 2, size: 42)
# You can access the attribute values – as usual
puts b.color #=> "red"
puts b.number #=> 2
puts b.size #=> 0.42e2
puts b.durability #=> undefined method `durability' [...]
# You can ask the instance about identifiers
puts b.identifier_for :color #=> "SOME__fancy_identifier"
puts b.identifier_for :durability #=> unknown API attribute :durability (ArgumentError)
# And you can ask the class about identifiers
puts Balls.identifier_for :color #=> "SOME__fancy_identifier"
puts Balls.identifier_for :durability #=> unknown API attribute :durability (ArgumentError)
Вам не нужен Virtus для реализации ваших идентификаторов API. Подобный вспомогательный модуль может просто зарегистрироваться attr_accessor
s вместо атрибутов Virtus.
Однако у Virtus есть и другие полезные функции, такие как хеш-конструкторы и атрибуты. Если вы не против жить без этих функций или найти замену, отказ от Virtus не должен быть проблемой.
НТН!:)
Да, вы должны продлить Virtus::Attribute
Я мог бы заставить его работать с:
module Virtus
class AttributeSet < Module
def define_identifier(attribute, method_name, visibility, identifier)
define_method(method_name) { identifier }
send(visibility, method_name)
end
end
class Attribute
def define_accessor_methods(attribute_set)
attribute_set.define_reader_method(self, name, options[:reader])
attribute_set.define_writer_method(self, "#{name}=", options[:writer])
attribute_set.define_identifier(self, "#{name}_identifier", options[:reader], options[:identifier])
end
end
end
Это может быть изменено, но вы можете c.case_number_identifier