Reputation: 42029
I'm wondering if you can help me write a scope method for Rails that I'm having a little trouble completing. I have an app where users can ask questions. The questions are classified according to both categories and tags i.e.
Question.rb has_many :categories
Question.rb has_many :tags
When I show a question, I want to have a feature that shows similar questions. In order to do that, I was going to show other questions according to the following conditions
First, at least one category is the same
Second, at least one tag is the same
Third, the other question has at least one answer already.
In my show action of the Questions controller, after I find the question, I then get the categories and tags for it
@question = Question.find(params[:id])
categories = @question.categories
tags= @question.tags
The local variable categories might look like this
[#<Category id: 3, name: "Wills & Estates", created_at: "2013-04-10 21:53:49", updated_at: "2013-04-10 21:53:49">, #<Category id: 4, name: "Business Law", created_at: "2013-04-10 21:53:49", updated_at: "2013-04-10 21:53:49">]
So what I do then is strip the name out of it like this
cats = Question.get_categories(categories)
with get_categories on the Question.rb model like this
def self.get_categories(categories)
categories = categories.map(&:name)
end
I do the exact same thing for tags
cats = Question.get_tags(tags)
Question.rb
def self.get_tags(tags)
tags = tags.map(&:name)
end
I then pass 'cats' and 'tags' (which are just strings of the categories and tags) into a scope on the Question.rb model
@similarquestions = Question.similar_questions(cats, tags)
This is where I get lost...
scope :similar_questions, lambda { |cats, tags|
joins(:categories).where(:categories { :name => cats})
joins(:tags).where(:tags { :name => tags })
}
The local variable 'cats' and 'tags' might be a single string, or they might be multiple strings depending on how many categories and tags a particular question has. My criteria for locating similar questions, as stated above, is to have one category the same and one tag the same (with the question also having at least one answer).
Could you give me some pointers as to what I might do inside this scope to accomplish what I'm trying to do. I'm wondering if I should be passing arrays in as local variables and do something like
scope :similar_questions, lambda { |cats, tags|
cats.each do |cat|
joins(:categories).where(:categories { :name => cat})
end
tags.each do |tag|
joins(:tags).where(:tags { :name => tag })
end
}
but even then I'm having trouble figuring out a way to achieve what I'm trying to do. One of my concerns is that I don't want a maze of if/then clauses in the scope. I'm sure there's a tidier way to do what I want, but I can't figure it out. One of the reasons I thought of passing in an array was to check if something was 'in' the array etc.
Thanks in advance if you can help.
Update
Question.rb has_many :answers
Upvotes: 0
Views: 57
Reputation: 35531
scope :similar_questions, lambda {|question|
joins(:categories, :tags).includes(:answers).
where( categories: {name: question.categories.pluck(:name)},
tags: {name: question.tags.pluck(:name)} ).
where("questions.id != ? AND answers.id IS NOT NULL", question.id)
}
Now you can do:
@similar_questions = Question.similar_questions(self)
Upvotes: 2