I have two active record queries:
feedbacks = Activity.where(subject_type: Feedback.name).select{ |f| f.subject.application == #application }
activities = Activity.where(subject: #application)
.order(created_at: :desc).page(params[:page])
.per(10) + feedbacks
I feel like there must be a better way to combine the results of these two queries. Also, pagination isn't going to work right since feedbacks could return n records.
If I added pagination to both queries, then I could get double the items than I actually want to display.
Edit: Here's an attempt which seems to be working, though - not pretty:
activities = Activity.where('subject_id = ? OR subject_type = ?', #application.id, Feedback.name)
.order(created_at: :desc)
.page(params[:page])
.per(20).select { |record|
if record.subject_type == Feedback.name
record.subject.application == #application
else
true
end
}
Add this to your Activity Model :
belongs_to :feedback, -> { where(activities: {subject_type: 'Feedback'}) }, foreign_key: 'subject_id'
then in your controller :
feedbacks = Activity.includes(:feedback).where(feedbacks:{application:#application})
other_activities = Activity.where(subject: #application)
matching_ids = (feedbacks.map(&:id)+other_activities.map(&:id))
activities = Activity.where(id:matching_ids).order(created_at: :desc).page(params[:page])
This solution is not really scalable, you should make quite a complex query because of two conditions based on polymorphic associations
Related
Basically I'd like to return all people whose current job title is X and whose previous job title is Y. As an example, I have a talent whose current emnployment is "Airbnb (company_id = 1)" and whose previous employment is at "Youtube (company_id = 2)".
If I run a query to find talent where current employment is Airbnb:
Talent.joins(:job_histories).where(["job_histories.company_id = ? and job_histories.end_year = ?", 1, "Present"])
I get the person.
If I run a query where previous employment is Youtube (hence the end_year != "Present" below)
Talent.joins(:job_histories).where(["job_histories.company_id = ? and job_histories.end_year != ?", 2, "Present"])
I also get the same person.
However, if I chain them together to find talents where current employer is Airbnb AND previous employer is Youtube, like this:
#talents = Talent.all
#talents = #talents.joins(:job_histories).where(["job_histories.company_id = ? and job_histories.end_year = ?", 1, "Present"])
#talents = #talents.joins(:job_histories).where(["job_histories.company_id = ? and job_histories.end_year != ?", 2, "Present"])
I do not get any results. I've tried several variations of the query but none return anything.
The only way I can get it to work is by using the first query and then looping over each talent to find where job_histories.company_id == 2.
if params[:advanced_current_company] && params[:advanced_previous_company]
#talents = #talents.joins(:job_histories).where(job_histories: { company_id: params[:advanced_current_company] }).distinct if params[:advanced_current_company]
#talents.each do |talent|
talent.job_histories.each do |job_history|
if job_history.company_id == params[:advanced_previous_company][0].to_i
new_talents.append(talent.id)
end
end
end
#talents = Talent.where(id: new_talents)
end
Any direction would be amazing. Thanks!
You had the right idea with a double join of the job_histories, but you need to alias the job_histories table names to be able to differentiate between them in the query, as otherwise activerecord will think it's only one join that needs to be done.
Talent.joins("INNER JOIN job_histories as jh1 ON jh1.talent_id = talents.id")
.joins("INNER JOIN job_histories as jh2 ON jh2.talent_id = talents.id")
.where("jh1.company_id = ? and jh1.end_year = ?", 1, "Present")
.where("jh2.company_id = ? and jh2.end_year != ?", 2, "Present")
I have a Convo table and a GroupMeeting table that both are associated with a Msg table.
I want to find all the instances where the current_user has convos or group_meetings with msgs, combine the two, and then show both together to the user in order of the last msg.created_at
Here I have defined both:
#convos = Convo.includes(:msgs).where("sender_id = ? OR recipient_id = ?", current_user, current_user).where.not(:msgs => { :id => nil }).merge(Msg.order(created_at: :desc))
#group_meetings = current_user.group_meetings.includes(:msgs).where.not(:msgs => { :id => nil }).merge(Msg.order(created_at: :desc))
And then combined them together:
#convos = #convos + #group_meetings
What I can't figure out is how to now sort them by msg.created_at
I have tried the following:
#convos = (#convos + #group_meetings).sort_by(&:"#{msg.created_at}")
#convos.order('msg.created_at DESC')
These all seem to be server-side sorting though. How can I sort these based off the join table, after the array has been created?
Please let me know if I need to supply any other details. Thank you!!
You can try the following:
(#convos + #group_meetings).sort_by { |item| item.msgs.minimum(:created_at) }
I have two queries, I need an or between them, i.e. I want results that are returned by either the first or the second query.
First query is a simple where() which gets all available items.
#items = #items.where(available: true)
Second includes a join() and gives the current user's items.
#items =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
I tried to combine these with Rails' or() method in various forms, including:
#items =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
.or(
#items
.joins(:orders)
.where(available: true)
)
But I keep running into this error and I'm not sure how to fix it.
Relation passed to #or must be structurally compatible. Incompatible values: [:references]
There is a known issue about it on Github.
According to this comment you might want to override the structurally_incompatible_values_for_or to overcome the issue:
def structurally_incompatible_values_for_or(other)
Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
(Relation::MULTI_VALUE_METHODS - [:eager_load, :references, :extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
(Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
end
Also there is always an option to use SQL:
#items
.joins(:orders)
.where("orders.user_id = ? OR items.available = true", current_user.id)
You can write the query in this good old way to avoid error
#items = #items.joins(:orders).where("items.available = ? OR orders.user_id = ?", true, current_user.id)
Hope that helps!
Hacky workaround: do all your .joins after the .or. This hides the offending .joins from the checker. That is, convert the code in the original question to...
#items =
#items
.where(orders: { user_id: current_user.id})
.or(
#items
.where(available: true)
)
.joins(:orders) # sneaky, but works! 😈
More generally, the following two lines will both fail
A.joins(:b).where(bs: b_query).or(A.where(query)) # error! 😞
A.where(query).or(A.joins(:b).where(bs: b_query)) # error! 😞
but rearrange as follows, and you can evade the checker:
A.where(query).or(A.where(bs: b_query)).joins(:b) # works 😈
This works because all the checking happens inside the .or() method. It's blissfully unaware of shennanigans on its downstream results.
One downside of course is it doesn't read as nicely.
I ran into the same issue, however the code was defined in a different place and was very difficult to change directly.
# I can't change "p"
p = Post.where('1 = 1').distinct # this could also be a join
And I needed to add an or statement to it
p.or(Post.where('2 = 2'))
The following code won't raise an error, because it has distinct like the initial relationship.
p.or(Post.where('2 = 2').distinct)
The problem with it it that it only works as long as you know the relationship. It may or not have a join, or distinct.
This works regardless of what the relationship is:
p.or(p.unscope(:where).where('2 = 2'))
=> SELECT DISTINCT `posts`.* FROM `posts` WHERE ((1 = 1) OR (2 = 2))
It occurs when you try to combine two multi-active records of the same type, but one of them has a joins value or an includes value, or in your case a reference value, that the other does not.
Therefore we need to match the values between them, and I found a general way to do this without knowing the actual values in advance.
items_1 = #items.joins(:orders)
.where(orders: { user_id: current_user.id})
items_2 = #items.where(available: true)
.joins(items_1.joins_values)
.includes(items_1.includes_values)
.references(items_1.references_values)
#items = items_1.or(items_2)
just solve it!
def exec_or_statement(q1, q2)
klass = q1.klass
key = klass.primary_key
query_wrapper_1 = {}
query_wrapper_1[key] = q1
query_wrapper_2 = {}
query_wrapper_2[key] = q2
klass.where(query_wrapper_1).or(klass.where(query_wrapper_2))
end
query_1 = #items.where(available: true)
query_2 =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
exec_or_statement(query_1, query_2)
I'm trying to find all associated records where two conditions are met.
I'm trying this, but it doesn't work, examples:
#students = #group.students
.includes(:attendances)
.where.not(attendances: {student_id: #ids, event_time_id: #event_id})
#students = #group.students
.includes(:attendances)
.where.not(attendances: {event_time_id: #event_id})
.where.not(attendances: {student_id: #ids})
#students = #group.students
.includes(:attendances)
.where("attendances.student_id IN (?) AND NOT attendances.event_time_id = ?", #ids, #event_id)
I want to get all the student records that exist in group and not have attendance with #event_id or have not attendance at all.
I think this might do what you want (untested), not the most elegant looking code...
exists_fragment = "SELECT 1 FROM attendances WHERE student_id = students.id"
#group.students
.joins(:attendance)
.where("attendances.event_id <> ? OR NOT EXISTS(?)", #event_id, exists_fragment)
Working solution:
#students = #group.students.includes(:attendances)
.where("attendances.event_time_id != ?", #event_id)
.references(:attendances)
#students_on = #group.students.includes(:attendances)
.where(attendances: { event_time_id: #event_id })
#students = #students - #students_on
Not sure if this is the best solution but it solves the mentioned problems
UPDATE
Less code version:
#students = #group.students
#students_exist = #group.students.includes(:attendances)
.where(attendances: { event_time_id: #event_id })
#students = #students - #students_exist
I'm trying to figure out a better way to have one query here. I want to be able to send something to last where statement a wildcard so I can select all vendors. Right now if i don't include that line it doesn't filter by vendor so I essentially get all the purchase requests.
Any thoughts of a cleaner way to do these sorts of queries?
if #vendor == "0" #checks for vendor
#purchase_requests = PurchaseRequest.includes(:purchase_order)
.where(:created_at => #date_start..#date_end)
.where(:total_cost => #cost_beginning..#cost_end)
else
#purchase_requests = PurchaseRequest.includes(:purchase_order)
.where(:created_at => #date_start..#date_end)
.where(:total_cost => #cost_beginning..#cost_end)
.where("purchaseorder.VendorRef_ListID = ?", #vendor)
end
there must be some better solution, but try this
#purchase_requests = PurchaseRequest.includes(:purchase_order).where(created_at: #date_start..#date_end, total_cost: #cost_beginning..#cost_end)
#purchase_requests = #purchase_requests.where("purchaseorder.VendorRef_ListID = ?", #vendor) unless #vendor == "0"
Here is a simplified version:
#purchase_requests = PurchaseRequest
.includes(:purchase_order)
.where(created_at: #date_start..#date_end)
.where(total_cost: #cost_beginning..#cost_end)
#purchase_requests = #purchase_requests.where('purchase_orders.VendorRef_ListID = ?', #vendor) unless #vendor == '0'