Rails: order query by has and belongs to many presence - mysql

I have the following active record models:
class Catalog < ActiveRecord::Base
has_and_belongs_to_many :customers
end
class Customer < ActiveRecord::Base
has_and_belongs_to_many :catalogs
end
Now in my index, i want to list all customers sorted like this:
first the ones who already are member of the catalog, then all the others.
I have tried something like this:
#customers = Customer.all.joins('LEFT JOIN catalogs_customers ON catalogs_customers.customer_id = customers.id').order('catalogs_customers.catalog_id DESC, customers.company_name ASC')
That is near to my goal but i got all the customers who are member of a catalog (whatever it is) and then all the other customers.

Your question is a tiny bit unclear, but I can't comment to ask more, so I'll do my best regardless.
It sounds like you want to list all customers with priority given to those associated with one specific catalog, but your code samples don't tell us how you plan on setting that. I'll assume that you have an instance variable #catalog_id. Then you want to provide a condition on your join where you only select catalogs_customers with that catalog_id. So try something like:
#customers = Customer.all.joins('LEFT JOIN catalogs_customers ON catalogs_customers.customer_id = customers.id').
.where(catalogs_customers: { catalog_id: #catalog_id }).
order('catalogs_customers.catalog_id DESC, customers.company_name ASC')
Hope that helps.

Related

Best performance wise query to get parent of queried nested resource

I am implementing an availability model nested within a listing. Its for a rental app.
class Listing
has_many :availabilities, dependent: :destroy
end
class Availability
belongs_to :listing
end
availabilities table has start and end date columns.
I am writing a query through search form to find listings where availabilities are present and the date given in the form lies in between start and end dates fo those availabilities.
My query in a class method looks like:
def self.search(params)
date = params[:date]
listingsids = Availability.where('startdate <= ?', date).where('enddate >= ?', date).pluck('listing_id')
products = Listing.where(id: listingsids)
end
However i feel this is not efficient. I wish I can write Listing.joins(:availability) and then use it but rails won't allow it. I can only join the other way which will give me a relation with availability objects and I want listings i.e. parent resource.
How can I make it more efficient and reduce number of queries I am doing?
Will appreciate your help :)
You should be able to use joins on listing to get you availablity relations, joins works using the relation name, not the model name, so instead of joins(:availability) you should be using joins(:availabilities). Something like this should work and use just a single query for your case:
Listing.joins(:availablities).where('availability.startdate <= ?', date).where('availability.enddate >= ?', date)
notice that joins uses the relation name joins(:availabilities) but the string in the where uses the table name where('availability.startdate <=?', date)

ActiveRecord: chaining scopes and queries on associated records

I came across a piece of ActiveRecord behavior that I find totally intuitive, but it took me by surprise. Given,
class User < ActiveRecord::Base
has_one :profile
scope :active, -> { where(status: 1) }
def self.find_by_code(code)
Profile.find_by_code(code).person
end
end
class Profile < ActiveRecord::Base
belongs_to :user
end
I can combine the active scope and the find_by_code class method like,
User.active.find_by_code("ABC123")
and it will find the profile with the given code, then use the user_id on that profile to find the associated person. AND it remembers the active scope. How does it do that?
You can see in the generated queries that it first retrieves a profile, then queries for the relevant person:
SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`code` = 'ABC123' LIMIT 1
SELECT `user`.* FROM `user` WHERE `user`.`active` = 1 AND `user`.`id` = 8 LIMIT 1
Unrelated, but I'm on Rails 3. I know the finder methods have changed a bit since then.

Rails 4 - order by related models created_at - activity

In my rails app I have few related models for example:
Event
has_many :comments
has_many :attendents
has_many :requests
What I need is to order by 'created_at' but not only main model (Event) but also related models, so I will display on top of the list event with most recent activity i.e.: comments, attendents, requests
So if Event date is newer than any comment, request or attendent date this event will be on top.
But if there is an event with newer comment this one will be on top etc.
How should I implement such ordering?
EDIT
db is mysql
Thanks
I would place a column on the event for last_activity, and maintain it by touching from the associated models.
The alternative is to order by using a join or subquery against the other tables, which is going to require a database query that will be much less efficient than simply ordering by last_activity descending.
Event
.select("events.*")
.joins("LEFT OUTER JOIN comments ON comments.event_id = events.id")
.joins("LEFT OUTER JOIN attendents ON attendents.event_id = events.id" )
.joins("LEFT OUTER JOIN requests ON requests.event_id = events.id")
.group("events.id")
.order("GREATEST(events.created_at,
MAX(comments.created_at),
MAX(attendents.created_at),
MAX(requests.created_at)) DESC")
If I understand your question correctly, something like this
Firstly add this to your Models in question:
default_scope order('created_at DESC')
Then I would do some grouping:
#comments = Comment.all
#comments_group = #comments.group_by { |c| c.event}
And in your view you'll be able to loop through each block:
#comments_group.each do |event, comment|
- event.name
comment.each do|c|
- c.body
I did not test this but it should give you an idea.

Sorting associated objects based on the association's creation date

Right now, I'm working on a simple app. It requires to get the associated objects ordered by the date that they we're added to the object. For that, I want to order them based on the pivot-table's id.
My app looks a bit like this:
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
end
However, when a user wants to buy a product, I would add a new relation into the pivot table courses_users. When I then run #product.users, I will get them back in the order the users where created, not added as the relation.
I've tried creating a query scope, but it didn't work. I also tried to create a order on the has_and_belongs_to_many, as such:
has_and_belongs_to_many :users, order: 'course_users.id ASC'
But none of that seemed to work, no ORDER statement could be found in the logs.
Add the created_at field to your table.
rails g migration AddTimestampsToCourseUsers created_at:datetime
then you can
#product.users.order "course_users.created_at ASC"

Rails merging tables

I have two tables: Members and Addresses. How can I merge these two tables together in my model, so that I could combine all their columns together? For example, Members has a column named position and Addresses has a column named street. How could I make it so that I could have Position and Address in the same virtual table. Is there a merge function for this?
Not sure why you need to create a separate model, you can use the basic Ruby constructs for this:
I'll assume Member has a column called address_id, that is a member belongs_to an Address. This will automatically associate the joins for you.
So all you need to do is something like this:
class Address < ActiveRecord::Base
end
class Member < ActiveRecord::Base
belongs_to :address
end
member = Member.create(:address => Address.find(123))
If you want to get the address and just the position field then go:
member = Member.find(456)
position = member.address.position
or perhaps:
Member.joins(:address).select("members.position, addresses.street")
Not sure what you are trying to do, but if it is something very specific, you could also try creating a View in your database and then making a model for it, treating it like a regular table.