Kathan Kathan - 4 months ago 7
Ruby Question

Interpolating an attribute's key before save

I'm using Rails 4 and have an

Article
model that has
answer
,
side_effects
, and
benefits
as attributes.

I am trying to create a
before_save
method that automatically looks at the side effects and benefits and creates links corresponding to another article on the site.

Instead of writing two virtually identical methods, one for side effects and one for benefits, I would like to use the same method and check to assure the attribute does not equal
answer
.

So far I have something like this:

before_save :link_to_article

private

def link_to_article
self.attributes.each do |key, value|
unless key == "answer"
linked_attrs = []
self.key.split(';').each do |i|
a = Article.where('lower(specific) = ?', i.downcase.strip).first
if a && a.approved?
linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
else
linked_attrs.push(i.strip)
end
end
self.key = linked_attrs.join('; ')
end
end
end


but chaining on the key like that gives me an
undefined method 'key'
.

How can I go about interpolating in the attribute?

Answer

in this bit: self.key you are asking for it to literally call a method called key, but what you want, is to call the method-name that is stored in the variable key.

you can use: self.send(key) instead, but it can be a little dangerous. If somebody hacks up a new form on their browser to send you the attribute called delete! you don't want it accidentally called using send, so it might be better to use read_attribute and write_attribute.

Example below:

def link_to_article
  self.attributes.each do |key, value|
    unless key == "answer" 
      linked_attrs = []
      self.read_attribute(key).split(';').each do |i|
        a = Article.where('lower(specific) = ?', i.downcase.strip).first
        if a && a.approved?
          linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
        else
          linked_attrs.push(i.strip)
        end
      end
      self.write_attribute(key, linked_attrs.join('; '))
    end
  end
end

I'd also recommend using strong attributes in the controller to make sure you're only permitting the allowed set of attributes.


OLD (before I knew this was to be used on all attributes)

That said... why do you go through every single attribute and only do something if the attribute is called answer? why not just not bother with going through the attributes and look directly at answer?

eg:

def link_to_article
  linked_attrs = []
  self.answer.split(';').each do |i|
    a = Article.where('lower(specific) = ?', i.downcase.strip).first
    if a && a.approved?
      linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
    else
      linked_attrs.push(i.strip)
    end
  end
  self.answer = linked_attrs.join('; ')
end
Comments