Как сохранить строковый идентификатор в атрибуте модели

Я использую 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_accessors вместо атрибутов 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

Другие вопросы по тегам