Single virtual attribute definition for multiple f

2019-07-17 01:19发布

问题:

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?

回答1:

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.



回答2:

You can take advantage of ruby's meta programming define_method

Define virtual attributes for those list of column's like

['income','taxes'...].each do |attr|
    define_method("flexible_#{attr}") do |arg|
      eval "#{attr}"
    end

    define_method("flexible_#{attr}=") do |arg|
      eval "self.#{attr} = #{arg}.tr('$ ,', '') unless #{arg}.blank?"
    end 
end

Refer:

https://rubymonk.com/learning/books/2-metaprogramming-ruby/chapters/25-dynamic-methods/lessons/72-define-method

http://apidock.com/ruby/Module/define_method

http://apidock.com/ruby/Kernel/eval