Reputation: 4835
I am in a similar situation as in ICTylor's post here.
So I have:
user1=User.find(1);
user2=User.find(2);
written=Micropost.where("user_id=2");
written.class #=> ActiveRecord::Relation
written.length #=> 50
replied=Micropost.where("response = 2 ") #=> 1
replied.class #=> ActiveRecord::Relation
Now if I:
alltogether= written + replied;
alltogether.class #=> Array
alltogether.length #=> 51
However I would like something that would be equivalent to doing:
all_sql= Micropost.where("user_id = 2 OR response = 2")
all_sql.class #=> ActiveRecord::Relation
all_sql.length #=> 51
In other words I would like to somehow append the records found by one Micropost.where(...)
to the ones found by the other Micropost.where(...)
into an ActiveRecord::Relation object. Resulting in an equivalent of all_sql but reached in two steps.
A bit of explanation. This part of the application is designed to provide a twitter-like functionality for reply messages.
E.g.: When the user with User.id = 1
sends this message to User.id=2
:
@2: hey this is a reply.
The application will create a Micropost
with the following parameters:
@post= Micropost.create(user:1, content:"@2: hey this is a reply", response: 2)
So response
simply indicates the receiver id of the reply. In the case when the message is not of reply-type, then response = nil
.
Following this idea, I would like to be able to:
def replies_to(user)
Micropost.where("response = #{user.id}")
end
def written_posts_by(user)
Micropost.where("user_id = #{user.id}")
end
def interesting_posts(user)
replies= replies_to(user)
written= written_posts_by(user)
#And now the question arises!
new_relation= replies union written#<---How can I do this!?
end
Upvotes: 2
Views: 209
Reputation: 24815
The design itself has some problems, say what if a post replied to two or more receivers? Also there will be too much null data in table which is not good.
Anyway, based current design allowing one receiver only, some changes is necessary at model.
class User < ActiveRecord::Base
has_many :posts
has_many :replies, class_name: 'Post', foreign_key: 'response_id'
def written_posts
posts
end
def posts_replied_to_me
replies
end
end
Notes of the above changes:
(user)
Now for the interesting_posts
. Due to the above refactoring you no longer rely the above method to build an aggregated query as they have different structure. Patching as Mori mentioned is a solution but I myself prefer not to touch libs' if possible.
I would prefer a method to aggregate the queries dedicated to this case.
def interested_posts
Post.where(interesting_criteria)
end
private
def interesting_criteria
conditions = []
conditions << written_by_me
conditions << replied_to_me
conditions << foo_bar
conditions.join(' AND ')
end
def written_by_me
"user_id = #{self.id}"
end
def replied_to_me
"response_id = #{self.id}"
end
def foo_bar
"feel free to add more"
end
Upvotes: 1
Reputation: 124
I don't think there is a way built-in Rails to do unions on ActiveRecord::Relation as of today . You're better off writing a query with OR even if it is not so DRY.
With arel:
def interesting_posts(user)
posts = Micropost.arel_table
Micropost.where(posts[:response].eq(user.id).or(posts[:user_id].eq(user.id)))
end
With SQL:
def interesting_posts(user)
Micropost.where('response = ? OR user_id = ?', user.id, user.id)
end
Upvotes: 1
Reputation: 27789
This blog post discusses this issue and offers a patch to enable chainable unions in ActiveRecord.
Upvotes: 1