Rails : get fields from join tables in one single query - ruby-on-rails

Starting with rails, i want to create a request with dynamic selection and dynamic sorting, like following examples (in SQL):
select * from books join authors on author_id = books.id
where books.title like '%something%'
order by author.name, books.title
or
select * from books join authors on author_id = books.id
where books.title like '%something%'
order by books.title, author.name
Author has_many books, book belongs to author.
I code this with two nested loops. In the first case, Author (sorted by name) is read first then Book (sorted by title), in the second case, Book first then author.
I can then print together books fields and authors fields.
The loops must reflect the hierarchy of sort fields.
But many other fields exist, and dynamic selection/ordering may be any field(s).
Is there a way to write a single 'each' loop, where books fields and authors fields would be available together, like with above sql examples.
My problem is to get fields from several tables on one single line.
What would the 'find' request be?
Thanks for your help.

Your basic query would be something like:
#books = Book.where("title LIKE ?", "%{something}%").joins(:author).order("author.name ASC, books.title ASC")
As for controlling the sorting, you can break that into scopes that get conditionally added depending on your params.

Related

How can I filter products by multiple categories with ActiveRecord?

I'd like to add the ability to filter products by multiple categories to a Rails ecommerce application. I currently allow filtering products by category, but now I'd like to provide the ability to filter further.
For example, I'd like to allow a user to select "Men's" and "Outerwear" to display only products in both of those categories.
Knowing that supplying an array of category IDs in my Product query will find products in any of the specified categories, and hoping for a nice ActiveRecord-y type solution, I first tried adding multiple categories.id conditions in the query, but this didn't work out.
Product.joins(:categories).where(:'categories.id' => 123, :'categories.id' => 456)
The result here was that the first category ID was overwritten by the second.
And, of course, this will find products in either of the categories, rather than only products in both:
Product.joins(:categories).where(:'categories.id' => [123, 456])
Additionally, the solution I need should work with an arbitrary number of categories. It could be two, it could be three, or more.
After doing some research, I don't think there's a nice Rails-y way to do this, but I'm stuck on finding the actual correct solution.
My application is running Rails 5.2 with MariaDB.
Based on #muistooshort's comment above, I found this SO post with the solution I needed:
Selecting posts with multiple tags
Updating my query like so gave the products I wanted, those in ALL of the specified categories (lines wrapped for readability):
Product.joins(:categories).where(:categories => { :id => category_ids })
.having('count(categories.name) = ?', category_ids.size)
.group('products.id')

Combining joins with other joins or regular queries

I'd like to combine Solr join queries with regular queries. As an example, suppose I want to find all stores in Jyväskylä (Finland) selling guide books. If documents for stores have the fields city and productIds and documents for products have the fields productType and productId in my index, I'd expect something like this to work:
{!join from=productIds to=productId}productType:"guide book" city:Jyväskylä
However, join queries are a particular kind of LocalParams and those are effective for the entire query. Therefore, this query would select documents that have productType=guide book and city=Jyväskylä, which doesn't make any sense.
Worse, suppose I want to look for stores that carry guide books and are located in cities with a population over 1000 people. I'd need two joins for that (to select products and cities).
Of course, I can split this into a query (q) and a filter query (fq), but that limits me to two kinds of queries (so, either one regular query and one join query or two joins), and more importantly abuses the concept of queries and filter queries.
My question therefore: how can I combine regular and join queries and how can I have multiple join queries?
I think I've got it: there can be more than one filter query and each can have its own join clause. Solr's admin interface won't let you enter more than one query in the fq field, but it's possible through the "Raw Query Parameters" field.
I'm not sure how efficient this is, though.

ActiveRecord 4.2 includes and ordering by count

I'm writing a search for a project I'm working on. It is meant to be able to search the body of articles and produce a list of their authors, ordered by the number of matching articles and including the relevant articles only, not all of their articles.
I currently have the following query:
Author.includes(:articles).where('articles.body ilike ?', '%foo%').references(:articles)
The use of includes in this case makes it so that all the relevant articles (not all articles) are preloaded, that's exactly what I want. However, when it comes to ordering by the number of included articles, I'm not sure how to proceed.
I should note I want to do this in ActiveRecord because pagination will be applied after the query. Not after a Ruby solution.
I should note I'm using PostgreSQL 9.3.
Edit: using raw SQL
This seems to work on its own like so:
Author.includes(:articles).where('articles.body ilike ?', '%foo%').references(:articles).select('authors.*, (SELECT COUNT(0) FROM articles WHERE articles.author_id = authors.id) AS article_count').order('article_count DESC')
This works fine. However, if I add .limit(1) it breaks.
PG::UndefinedColumn: ERROR: column "article_count" does not exist
Any idea why adding limit breaks it? The query seems very different too
SELECT DISTINCT "authors"."id", article_count AS alias_0 FROM "authors" LEFT OUTER JOIN "articles" ON "articles"."author_id" = "authors"."id" WHERE (articles.body ilike '%microsoft%') ORDER BY article_count DESC LIMIT 1
I don't think there's an out of the box solution for this. You have to write raw sql to do this but you can combine it with existing ActiveRecord queries.
Author
.includes(:articles)
.select('authors.*, (SELECT COUNT(0) FROM articles WHERE articles.author_id = authors.id) AS article_count')
.order('article_count DESC')
So the only thing to explain here is the select part. The first part, authors.*, selects all fields under the authors table and this is the default. Since we want to also count the number of articles, we create a subquery and pass its result as one of the pseudo columns of authors (we called it article_count). The last part is to just call order using article_count.
This solution assumes a couple of things which you'll have to fine tune depending on your setup.
Author by convention in rails maps to an authors table. If it is an STI (inherits from a User class and is using users table), you'll need to change authors to users.
articles.author_id assumes that the foreign key is author_id (and essentially, an article is only written by a single author). Change to whatever the foreign key is.
So given that, you'll have an array of authors ordered by the number of articles they've written.

Can I Order Results Using Two Columns from Different Tables?

I have a Rails application featuring a city in the US. I'm working on a database process that will feature businesses that pay to be on the website. The goal is to feature businesses within an hour's drive of the city's location in order to make visitors aware of what is available. My goal is to group the businesses by city where the businesses in the city are listed first then the businesses from the next closest city are displayed. I want the cities to be listed by distance and the businesses within the city group to be listed by the business name.
I have two tables that I want to join in order to accomplish this.
city (has_many :businesses) - name, distance
business (belongs_to :city) - name, city_id, other columns
I know I can do something like the statement below that should only show data where business rows exist for a city row.
#businesses = City.order(“distance ASC").joins('JOIN businesses ON businesses.city_id = cities.id')
I would like to add order by businesses.name. I've seen an example ORDER BY a.Date, p.title which referencing columns from two databases.
ORDER BY a.Date, p.title
Can I add code to my existing statement to order businesses by name or will I have to embed SQL code to do this? I have seen examples with other databases doing this but either the answer is not Rails specific or not using PostgreSQL.
After lots more research I was finally able to get this working the way I wanted to.
Using .joins(:businesses) did not yield anything because it only included the columns for City aka BusinessCity and no columns for Business. I found that you have to use .pluck or .select to get access to the columns from the table you are joining. This is something I did not want to do because I foresee more columns being added in the future.
I ended up making Business the main table instead of BusinessCity as my starting point since I was listing data from Business on my view as stated in my initial question. When I did this I could not use the .joins(:business_cities) clause because it said the relation did not exist. I decided to go back to what I had originally started with using Business as the main table.
I came up with the following statement that provides all the columns from both tables ordered by distance on the BusinessCity table and name on the Business table. I was successful in added .where clauses as needed to accommodate the search functionality on my view.
#businesses = Business.joins("JOIN business_cities ON business_cities.id = businesses.business_city_id").order("business_cities.distance, businesses.name")

Rails - Only pull in some HABTM associations on a case-by-case basis to avoid unnecessary joins

In Rails 4, I have a project in which I've set up three models with the following many-to-many relationships:
An Item
has_and_belongs_to_many categories
has_and_belongs_to_many tags
A Category
has_and_belongs_to_many items
A Tag
has_and_belongs_to_many items
And while it's easy to select an Item and automatically get all associated categories and tags, there are some situations in which I'd want to select items AND their associated categories, but NOT their tags. In these cases, I'd like to avoid doing extra database joins against the Tags table and ItemsTags join table. Can anyone help me with the correct find syntax to only join Items to categories? (Side note: I'm also planning on adding 10 additional many-to-many relationships between items and other models, but I'm just simplifying the scenario for this question. In the end, I'm trying to avoid doing a join with an excessive number of tables whenever I can.)
Thanks!
Rails will by default not load associated records unless you request it
Item.all will only fetch record from 'items' table
Then later in your code if you call item.categories that's the point when a query is performed to fetch all categories of this particular item. If you never call item.tags then the query to 'tags' table is never executed and the records are not fetch. Bottom line is: you can have as many associations as needed, as long as you don't explicitly call them they won't be loaded.
Side note about performance, rails offer several ways to join and include associated tables:
Item.include(:category).all Will trigger only 2 queries to fetch all items, and all associated categories.
Item.include(:category).joins(:category).all -> will trigger only 1 query joining the items and categories tables (but it may be slower than 2 requests)
So you have all control over what's loaded from the database. Those can apply for scope as well.

Resources