Jack Moore

Email: jack(at)jmoore53.com
Project Updates

Metaprogramming Rails Helper Module for Accessible Attributes

16 Apr 2020 » ruby, attributes, getters, setters, object, dynamic programming languages

Dynamically creating instance methods:

module RundeckHelper

    def self.attr_value(*args)
        # Singleton Very Important
        singleton_class = class << self; self; end
        args.each do |key|
            name = key
            key = key.to_s.upcase
            val = nil
            if !ENV[key].nil?
                if ENV[key] != ""
                    val = ENV[key]
                else
                    val = nil
                end
            elsif !Rails.application.credentials.rundeck.nil?
                key2 = key.downcase
                if !Rails.application.credentials.rundeck[key2].nil?
                    val = Rails.application.credentials.rundeck[key2]
                else
                    val = nil
                end
            elsif !RundeckConfigurationOption.find_by(name: key).nil?
                val = RundeckConfigurationOption.find_by(name: key).value
            else
                val = nil
            end
            # Send the method to the instance
            singleton_class.send(:define_method, name) do
                return val
            end
        end
    end

    attr_value :base_url, :create_job_id, :project_id, :create_environment_id, :create_instance_id
end

What is this code and how does it work?

Going line by line, this code exists in a Rails module. This is why I believe this was difficult. Creating custom attr_’s had to be hacked as everything in this module is called as an instance method. The module isn’t instantiated like a class would be. This module in particular has all its methods called as instance methods. The module basically provides helpers to the classes that require this module.

Breaking down the key lines:

  • def self.attr_value(*args)
  • singleton_class = class << self; self; end
  • singleton_class.send(:define_method, name) do
  • attr_value :base_url, :create_job_id, :project_id, :create_environment_id, :create_instance_id

These three lines are crucial to creating custom attributes for the variables passed into the attr_value function.

Starting from the top: def self.attr_value(*args)

  • This line defines the method and the method accepts multiple arguments.
  • It is an instance method meaning it can be called from within the module

Moving on: singleton_class = class << self; self; end

  • We define a singleton_class inside this method to accept from the current object which is the module

Sending Methods: singleton_class.send(:define_method, name) do

  • We take the singleton_class we created that is attached to the module and send it the method
  • the name variable, as seen above, is just the name of the symbol that was passed

Calling the Attribute: attr_value :base_url, :create_job_id, :project_id, :create_environment_id, :create_instance_id

  • basically just instantiates the values we need and allows us to use the methods RundeckHelper.base_url and self.base_urlreturning the value stored in the environment, in the secrets, or in the database.

Easier Example

module MessingAround
    def self.attr_valid(*args)
        singleton_class = class << self; self; end
        args.each do |key|
            val = self.find_val(key.to_s)
            name = key.to_s + "_valid?"
            singleton_class.send(:define_method, name) do
                return "#{val}"
            end
        end
    end

    def self.find_val(val)
        if val == "migration"
            return true
        else
            return false
        end
    end

    attr_valid :migration, :backup 

end

puts MessingAround.migration_valid?
puts MessingAround.backup_valid?
ruby /tmp/messing_around.rb
# returns:
true
false
© Jack Moore - This site was last built Fri 30 Aug 2024 12:31:24 PM EDT