Rails: How to include all associations with where condition - ruby-on-rails

I'm using Rails 6.1.4 and I have a Contact model having a has_one relationship with a Client model. A client has a status column.
However, Not all contacts have a client, but I want to return all contacts where clients have status as active, inclusive of contacts not having a client.
I tried this query but it only returns objects having a client associated.
#contacts = #contacts.eager_load([:client, :organization, :addresses]).where("clients.status = 'Active'").all
Can someone please help me with a query?

Untested, but should be headed in the right direction:
#contacts.eager_load([:client, :organization, :addresses]).where("clients.status = 'Active'").or(where("contacts.client_id IS NULL").all
Here's a trick I use: examine the SQL generated by your query:
Contact.eager_load([:client, :organization, :addresses]).where("clients.status = 'Active'").to_sql
And see what that gets you, then you can play with find_by_sql:
Contact.eager_load(...).find_by_sql(
SELECT ... FROM contacts
JOIN ...
WHERE contacts.client_id IS NULL
OR clients.status = 'Active'
)

Related

How to Sort Record in Ruby on Rails based on Last Record Timestamp from References Table

I need to create a live chat app and now I have three models :
ChatRoom
ChatRoomMember
ChatRoomMessage
From this three models, I use includes and references to get the list of chat_rooms for current login user. Here is the code that I have wrote.
#chat_rooms = ChatRoom.includes(:members).references(:members)
#chat_rooms = #chat_rooms.includes(:messages).references(:messages)
#chat_rooms = #chat_rooms.where 'chat_room_members.user_id = ?', #current_user.id
#chat_rooms = #chat_rooms.order 'chat_room_messages.created_at DESC'
#chat_rooms = #chat_rooms.limit(limit).offset(offset)
However, the order didn't work as I expected. What I want is that the chat_rooms are sorted by created_at column from the last message in that room. How can I do this ?
Here is the database structure :
Use association to avoid where 'chat_room_members.user_id = ?', #current_user.id
Here is my suggestion, assuming User has associations looking like:
class User
has_many :chat_room_members
has_many :chat_rooms, through: :chat_room_members
end
# list only rooms with at least on message
#chat_rooms = #current_user.chat_rooms.joins(:messages).order('chat_room_messages.created_at DESC').limit(limit).offset(offset)
# list all rooms even if there is no message attached
#chat_rooms = #current_user.chat_rooms.distinct.left_joins(:messages).order('chat_room_messages.created_at DESC').limit(limit).offset(offset)
Try this:
ChatRoom.includes(:messages).order('chat_room_messages.created_at DESC')
Thanks for everyone has help me to solve this probel. I have my own answer. Here is it.
SELECT chat_rooms.*, (SELECT chat_room_messages.created_at FROM chat_room_messages WHERE chat_room_messages.chat_room_id = chat_rooms.id ORDER BY chat_room_messages.id DESC LIMIT 1) AS last_message_at FROM chat_rooms INNER JOIN chat_room_members ON chat_room_members.chat_room_id = chat_rooms.id WHERE chat_room_members.user_id = #{#current_user.id} ORDER BY last_message_at DESC
I use raw query for solve this problem. Hope this can help anyone who need it !

Rails Active Record Query joins only, any

Student has_many :enrollments
With this query I see those students that have true enrollments, but also those that have both, true and false enrollments:
#students = Student.joins(:enrollments).where(enrollments: { is_active: false })
Is there some "only" attribute that I can add to see students that have only active enrollments?
One way straightforward would be to find the students that inactive enrolments and then explicitly exclude them. Something like:
have_inactives = Enrollment.where(is_active: false).select(:student_id)
#students = Student.joins(:enrollments).where.not(id: have_inactives)
The joins(:enrollments) will filter out Student entries that don't have any enrolments and the where.not(...) will exclude all those students that have inactive enrolments (using a subquery so all the work will still be inside the database where it belongs).
BTW, you might want to fix your spelling of "enrolment", the double-l misspelling will probably end up driving you or someone other programmer crazy.

Postgresql cross-database references in Rails

I'm building a search for and got stuck on a cross-database references error
activities = Activity.order(:name).includes([:profile => :country])
activities = activities.where("lower(activities.city) like ?", "%#{params[:activity_search][:city].downcase}%") unless params[:activity_search][:city] == ""
activities = activities.where("activities.sport_id =?", params[:activity_search][:sport_id])
I'm trying to add something like this:
activities = activities.where("activities.profiles.country.id =?", params[:activity_search][:country_id])
an activity's country is the same as the activity creator's country.
How can I add this constraint in my query?
Thanks for your help
You need to use joins here to include the associations:
activities.joins(profiles: :country).where('countries.id = ?', params[:whatever])
This assumes Activity has_many :profiles and Profile has_one :country and countries is your table name, but I think all that is true based on your post. This will include the associated profiles and country with activities and allow you to use their attributes in the where method call.

How to write complex query in Ruby

Need advice, how to write complex query in Ruby.
Query in PHP project:
$get_trustee = db_query("SELECT t.trustee_name,t.secret_key,t.trustee_status,t.created,t.user_id,ui.image from trustees t
left join users u on u.id = t.trustees_id
left join user_info ui on ui.user_id = t.trustees_id
WHERE t.user_id='$user_id' AND trustee_status ='pending'
group by secret_key
ORDER BY t.created DESC")
My guess in Ruby:
get_trustee = Trustee.find_by_sql('SELECT t.trustee_name, t.secret_key, t.trustee_status, t.created, t.user_id, ui.image FROM trustees t
LEFT JOIN users u ON u.id = t.trustees_id
LEFT JOIN user_info ui ON ui.user_id = t.trustees_id
WHERE t.user_id = ? AND
t.trustee_status = ?
GROUP BY secret_key
ORDER BY t.created DESC',
[user_id, 'pending'])
Option 1 (Okay)
Do you mean Ruby with ActiveRecord? Are you using ActiveRecord and/or Rails? #find_by_sql is a method that exists within ActiveRecord. Also it seems like the user table isn't really needed in this query, but maybe you left something out? Either way, I'll included it in my examples. This query would work if you haven't set up your relationships right:
users_trustees = Trustee.
select('trustees.*, ui.image').
joins('LEFT OUTER JOIN users u ON u.id = trustees.trustees_id').
joins('LEFT OUTER JOIN user_info ui ON ui.user_id = t.trustees_id').
where(user_id: user_id, trustee_status: 'pending').
order('t.created DESC')
Also, be aware of a few things with this solution:
I have not found a super elegant way to get the columns from the join tables out of the ActiveRecord objects that get returned. You can access them by users_trustees.each { |u| u['image'] }
This query isn't really THAT complex and ActiveRecord relationships make it much easier to understand and maintain.
I'm assuming you're using a legacy database and that's why your columns are named this way. If I'm wrong and you created these tables for this app, then your life would be much easier (and conventional) with your primary keys being called id and your timestamps being called created_at and updated_at.
Option 2 (Better)
If you set up your ActiveRecord relationships and classes properly, then this query is much easier:
class Trustee < ActiveRecord::Base
self.primary_key = 'trustees_id' # wouldn't be needed if the column was id
has_one :user
has_one :user_info
end
class User < ActiveRecord::Base
belongs_to :trustee, foreign_key: 'trustees_id' # relationship can also go the other way
end
class UserInfo < ActiveRecord::Base
self.table_name = 'user_info'
belongs_to :trustee
end
Your "query" can now be ActiveRecord goodness if performance isn't paramount. The Ruby convention is readability first, reorganizing code later if stuff starts to scale.
Let's say you want to get a trustee's image:
trustee = Trustee.where(trustees_id: 5).first
if trustee
image = trustee.user_info.image
..
end
Or if you want to get all trustee's images:
Trustee.all.collect { |t| t.user_info.try(:image) } # using a #try in case user_info is nil
Option 3 (Best)
It seems like trustee is just a special-case user of some sort. You can use STI if you don't mind restructuring you tables to simplify even further.
This is probably outside of the scope of this question so I'll just link you to the docs on this: http://api.rubyonrails.org/classes/ActiveRecord/Base.html see "Single Table Inheritance". Also see the article that they link to from Martin Fowler (http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html)
Resources
http://guides.rubyonrails.org/association_basics.html
http://guides.rubyonrails.org/active_record_querying.html
Yes, find_by_sql will work, you can try this also:
Trustee.connection.execute('...')
or for generic queries:
ActiveRecord::Base.connection.execute('...')

ActiveRelation where statement on child attribute

I have a has_one condition that I'm trying to access but am having a little trouble
Solicitation belongs_to :lead
Lead has_many :solicitations
My first statement grabs all solicitations for a user
#solicitations = current_user.solicitations.includes(:lead)
I can already access the attribute lead.case_type and could just cycle through the relation and put them in their places manually, but I figure their is an easier way.
What I am trying to do is something similar to
#solicitations.where("lead.case_type = ?", "Civil")
I have tried these and receive an unknown column error lead.case_type
Solicitation.all(:conditions => {:lead => {:case_type => 'Civil'}}, :joins => :lead)
The problem is that you are using lead.case_type, but (if you're following Rails' conventions) your table name is leads. This should work:
#solicitations = current_user.solicitations.includes(:lead).where("leads.case_type = ?", "Civil")
You could also use joins for that:
#solicitations = current_user.solicitations.joins(:lead).where("leads.case_type = ?", "Civil")
includes does an outer join, whereas joins does an inner join. Since you're querying the joined table an inner join would be better here.
In where you always have to use the table name (plural), but in includes and joins it depends on the relationship. In this case solicitation belongs to lead, so you have to use :lead (singular). It's a bit confusing, but I hope this clears it up for you.

Resources