Search code examples
ruby-on-railsassociationspaper-trail-gem

How to store foreign keys in the parent with a has_many relationship


I need to mimic a standard Rails has_many relationship, but with the foreign keys stored in the parent.

class Product < ActiveRecord::Base
  has_many :questions
end

class Question < ActiveRecord::Base
  belongs_to :product
end

This is because I am using PaperTrail for versioning, and when I retrieve an earlier version of a @product, I want to see what questions it was associated with.

So far, I'm thinking of creating:

  • An association callback that updates a serialized array of question ids every time a question is added to or removed from @product.questions
  • A method that read this array and turns it into a collection of questions

Something like:

class Product < ActiveRecord::Base

  serialize :questions_list, Array

  has_many :questions, :after_add => :update_questions_list, :after_remove => :update_questions_list

  def update_questions_list
    update_column :questions_list, questions.map{|q| q.id}
  end

  def versioned_questions
    questions_list.map{|id| Question.find(id)}
  end

end

Then I'd exclusively reference versioned_questions in my other methods.

But this seems a bit hackish and potentially a source of bottlenecks. I'd like to do something natively Railsish if possible, where I get all that ActiveRecord Association goodness automatically. Can I?

BTW there is a StackOverflow question that, from it's title, looks like it answers my question, but it actually relates to has_one associations, not has_many.


Solution

  • The final answer, using Rails 4's support for PostgresQL's array column type for questions_list, was:

    class Product < ActiveRecord::Base
    
      has_many :questions, :after_add => :add_to_questions_list, :after_remove => :remove_from_questions_list
    
      def add_to_questions_list(question)
        update_attribute :questions_list, questions_list << question.id
      end
    
      def remove_from_questions_list(question)
        update_attribute :questions_list, questions_list.reject{|i| i == question.id}
      end
    
      def versioned_questions
        questions_list.map{|id| Question.find(id)}
      end
    
    end