Search code examples
ruby-on-railsrubyvirtual-attribute

Single virtual attribute definition for multiple fields


I'm wondering if there is a way to set a virtual attribute so that it can handle multiple fields/variables.

Basically, I have several different integer columns in my model (income, taxes) and for each of them I need to check when the form is submitted, and remove illegal characters, which I'm doing by setting a virtual attribute and using tr to strip the characters.

def flexible_income
  income
end

def flexible_income=(income)
  self.income = income.tr('$ ,', '') unless income.blank?
end

And then I'm setting strong parameters in the controller as should be done:

params.required(:tax).permit(:flexible_income, :flexible_taxes)

The problem is I have many fields (more than just the two I listed above), and so for each of those fields, (many of which just need to check for the exact same illegal characters) I have to use a new virtual attribute and basically just repeat the same code:

def flexible_taxes
  taxes
end

def flexible_taxes=(taxes)
  self.taxes = taxes.tr('$ ,', '') unless taxes.blank?
end

Is there anyway to just set a common shared attribute for many different fields, while still being able to set the strong parameters?


Solution

  • You can always use meta programming to dynamically generate all this repeated code.

    module Flexible
      FLEXIBLE_ATTRIBUTES.each do |attr|
        define_method("flexible_#{attr}") do
          self.send(attr)
        end
    
        define_method("flexible_#{attr}=") do |val|
          self.send("#{attr}=", val.tr('$ ,', '')) unless val.blank?
        end  
    
      end
    end
    

    Now include this module in you class and define the FLEXIBLE_ATTRIBUTES:

    class MyModel
      FLEXIBLE_ATTRIBUTES = [:income, :taxes] #......
      include Flexible
    end
    

    Now I haven't tested this code but it should do what you're looking for with minimum repetitiveness. It also separates the Flexible method creation logic outside of the model itself. It will also allow you to reuse this module in other classes if needed.