Search code examples
ruby-on-railsrubyruby-on-rails-4modelbefore-save

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?


Solution

  • 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