Rails - Activerecord conditions with enum - ruby-on-rails

I have an Item model and a UserItem model. An Item has many UserItems.
Both models use Enum:
enum status: [ :pending, :approved]
I need to query Item to return user_items where item.id != 1, item.status is approved, and user_items.status is pending. I am having trouble with the correct syntax
Item.joins(:user_items).where( "items.id != ? and items.status = ? and user_items.status = ?", 1, ???, ???)
What is the correct way to write this query?

Since you're selecting UserItems, and assuming you have belongs_to :item on the other side of has_many relation query will look like
UserItem.pending.joins(:item).merge(Item.approved.where.not(id:1))

Try this-
UserItem.joins("LEFT OUTER JOIN items.id = user_items.id").where("items.id != ? AND items.status = ? AND user_items.status = ?", 1, "approved","pending")

In case if you don't want to use scope
Item.joins(:user_item).where( "items.id != ? and items.status = ? and user_items.status = ?", 1, Item.statuses[:pending], UserItem.statuses[:approved])
But it is always nicer to use scopes.

Related

Activerecord query where current employer is X and previous employer is Y

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")

rails find_by returns incorrect record?

I have an app where a User creates a Transaction to purchase an Item from a different User. I am suddenly having difficulty with the find_by in one method on Item. I want to find the very first Transaction involving the Item on which it is called, and I want to further limit that result by searching against a number of invalid states.
class Item < ApplicationRecord
def first_find
Transaction.find_by("item_id = ? AND recipient_id = ? AND state != ? OR state != ? OR state != ?", self.id, author.id, :ignored, :declined, :unapproved)
end
end
What this does, no matter what, is return the very first Transaction in my db. This is not expected behavior. So in console if I go like t = Transaction.last to cache Transaction id #5 (which has an item_id of 7 and a recipient_id of 4), and then call t.item.first_find, I would presumably get Transaction #5. The SQL output for this is query is Transaction Load (1.1ms) SELECT "transactions".* FROM "transactions" WHERE (item_id = 7 AND recipient_id = 4 AND state != 'ignored' OR state != 'declined' OR state != 'unapproved') LIMIT $1 [["LIMIT", 1]].
Which is great! That's what I want from the output. But to my confusion, it returns this:
#<Transaction id: 2, sender_id: 1, recipient_id: 2, item_id: 9 ..... >
Does anyone have any idea why? Thanks!
Edit 1
So I think I've solved it? I've had this problem before where putting too many search params into the where clause messes it up for some reason.
So while this does not work
Transaction.find_by("item_id = ? AND recipient_id = ? AND state != ? OR state != ? OR state != ?", self.id, author.id, :ignored, :declined, :unapproved)
This does
Transaction.where("item_id = ? AND recipient_id = ?", self.id, author.id).where("state != ? OR state != ? OR state != ?", :ignored, :declined, :unapproved).first
I'm not entirely sure why, though. Does anyone know?
Edit 2
The AND operators should be separate from the OR operators.
answering why.
that's how SQL operator precedence works. more explanation is here. so when you break it to another "where" clause that builds a new relation, which is the result of filtering the current relation according to the conditions in the arguments. the source code is here.
let me show other solutions.
1.
Transaction.where(item_id: self.id, recipient_id: author.id).where.not(state: [:ignored, :declined, :unapproved]).first
2.
recipient_transactions = Transaction.where(item_id: self.id, recipient_id: author.id)
active_transactions = Transaction.where.not(state: [:ignored, :declined, :unapproved])
result = recipient_transactions.merge(active_transactions).first # this buils a single query
I think you should use where clause instead of using find_by,
class Item < ApplicationRecord
def first_find
Transaction.where("item_id = ? AND recipient_id = ? AND state != ? OR state != ? OR state != ?", self.id, author.id, :ignored, :declined, :unapproved)
end
end
this will return ActiveRecord::Relation(record collections) instead of just one record if you using find statement

Scope Order by Count with Conditions Rails

I have a model Category that has_many Pendencies. I would like to create a scope that order the categories by the amount of Pendencies that has active = true without excluding active = false.
What I have so far is:
scope :order_by_pendencies, -> { left_joins(:pendencies).group(:id).order('COUNT(pendencies.id) DESC')}
This will order it by number of pendencies, but I want to order by pendencies that has active = true.
Another try was:
scope :order_by_pendencies, -> { left_joins(:pendencies).group(:id).where('pendencies.active = ?', true).order('COUNT(pendencies.id) DESC')}
This will order by number of pendencies that has pendencies.active = true, but will exclude the pendencies.active = false.
Thank you for your help.
I guess you want to sort by the amount of active pendencies without ignoring categories that have no active pendencies.
That would be something like:
scope :order_by_pendencies, -> {
active_count_q = Pendency.
group(:category_id).
where(active: true).
select(:category_id, "COUNT(*) AS count")
joins("LEFT JOIN (#{active_count_q.to_sql}) AS ac ON ac.category_id = id").
order("ac.count DESC")
}
The equivalent SQL query:
SELECT *, ac.count
FROM categories
LEFT JOIN (
SELECT category_id, COUNT(*) AS count
FROM pendencies
GROUP BY category_id
WHERE active = true
) AS ac ON ac.category_id = id
ORDER BY ac.count DESC
Note that if there are no active pendencies for a category, the count will be null and will be added to the end of the list.
A similar subquery could be added to sort additionally by the total amount of pendencies...
C# answer as requested:
method() {
....OrderBy((category) => category.Count(pendencies.Where((pendency) => pendency.Active))
}
Or in straight SQL:
SELECT category.id, ..., ActivePendnecies
FROM (SELECT category.id, ..., count(pendency) ActivePendnecies
FROM category
LEFT JOIN pendency ON category.id = pendency.id AND pendnecy.Active = 1
GROUP BY category.id, ...) P
ORDER BY ActivePendnecies;
We have to output ActivePendnecies in SQL even if the code will throw it out because otherwise the optimizer is within its rights to throw out the ORDER BY.
For now I developed the following (it's working, but I believe that it's not the best way):
scope :order_by_pendencies, -> { scoped = Category.left_joins(:pendencies)
.group(:id)
.order('COUNT(pendencies.id) DESC')
.where('pendencies.active = ?', true)
all = Category.all
(scoped + all).uniq}

Find record which doesn't have any associated records with a specific value

I have a couple of models: User and UserTags
A User has_many UserTags
A UserTag belongs_to User
I am trying to find all the Users which don't have a UserTag with name 'recorded' (so I also want users which don't have any tags at all). Given a users relation, I have this:
users.joins("LEFT OUTER JOIN user_tags ON user_tags.user_id = users.id AND user_tags.name = 'recorded'").
where(user_tags: { id: nil })
Is there any other better or more Railsy way of doing this?
Try this:
users.joins("LEFT OUTER JOIN user_tags ON user_tags.user_id=users.id").where("user_tags.name != ? OR user_tags.id is NULL", 'recorded')
This one should work:
users.joins(:user_tags).where.not(user_tags: { name: 'recorded' })
Joins not eager load nested model you should use "Includes or eage_load"
users.eager_load(:user_tags).where.not(user_tags: { name: 'recorded' })
This will use left outer join and you can update your query in where clause.
Same as
users.includes(:user_tags).where.not(user_tags: { name: 'recorded' })
Try this, It will return the users with 0 user_tags :
users = users.joins(:user_tag).where("users.id IN (?) OR user_tags.name != ?",User.joins(:user_tag).group("users.id").having('count("user_tag.user_id") = 0'), "recorded")
Hey you can simply use includes for outer join as user_tags.id is null returns all your record not having user_tags and user_tags.name != 'recorded' returns record having user_tag name is not recorded
users.includes(:user_tags).where("user_tags.id is null or user_tags.name != ?","recorded")
Or you can also used using not in clause as but it is not optimised way for given query:
users.includes(:user_tags).where("users.id not in (select user_id from user_tags) or user_tags.name != ?","recorded")

Combining two ActiveRecord queries with pagination

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

Resources