0 votes
1 view
in Salesforce by (11.9k points)

I'm using Virtus to create models that represent Salesforce objects.

I'm trying to create attributes that have friendly names that are used to access the value and method that I can use to retrieve a identifier "String" for that variable.

Object.attribute #=> "BOB"

Object.get_identifier(:attribute_name) #=> "KEY"

# OR something like this

Object.attribute.identifier #=> "KEY"

The friendly name is used as the getter/setter and a identifier that I can store each attribute corresponding to the API name.

Here is an example:

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'

Or, instead of having a method on the Attribute itself, maybe a secondary method gets created for each identifier set:

c.case_number #=> 'XXX'

c.case_number_identifier #=> 'Case_Number__c'

Could I extend Virtus::Attribute and add this? If so, I'm unsure on how to go about it.

1 Answer

0 votes
by (31.6k points)

You can use Monkey patching Virtus' Attribute class. 
However, moving into the internals of a library makes you exposed to refactorings in the private part of that libraries' source code.

Instead, you could use a helper module that encapsulates this feature. Here is a suggestion how:

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)

You don't need Virtus in order to perform your API identifiers. A related helper module could just register attr_accessors instead of Virtus attributes. 
However, Virtus has other handy features like the hash constructors and attribute conversion. If you don't mind living without these features or finding replacements, discarding Virtus should not be a problem.

Welcome to Intellipaat Community. Get your technical queries answered by top developers !


Categories

...