Rails - get objects of objects WITH duplicates - ruby-on-rails

I received some really good help in solving an issue in which I needed to get objects from objects in one query. That worked like a charm.
This is a follow up to that question. I chose to create a new question to this since the last one was answered according to my previous specification. Please do refer to that previous question for details.
Basically, I wanted to get all the objects of multiple objects in one single query. E.g. if a Product has several Categories which in turn has several Products, I want to get all the Products in that relation, easier (and erronously) put:
all_products = #my_product.categories.products
This was solved with this query. It is this query I would (preferably) like to alter:
Product.includes(:categories).where(categories: { id: #my_product.categories.pluck(:id) } )
Now, I realized something I missed using this solution was that I only get a list of unique Products (which one would expect as well). I would however like to get a list with possible duplicates as well.
Basically, if a "Blue, Electric Car" is included in categories ("Blue", "Electric" and "Car") I would like to get three instances of that object returned, instead of one unique.
I guess this does not make Rails-sense but is there a way to alter the query above so that it does not serve me a list of unique objects in the returned list but rather the "complete" list?

The includes method of AREL will choose between two strategies to make the query, one of which simply does two distinct query and the other one does an INNER JOIN.
In both cases the products will be distinct.
You have to do manually a right outer join:
Product.joins('RIGHT JOIN categories ON categories.product_id = products.id').where(categories: { id: #my_product.categories.pluck(:id) } )
adds also .preload(:categories) if you want to keep the eager loading of the categories.

Since you want duplicates, just change includes to joins, (I tested this just now). joins will essentially combine (inner-join) the two tables giving you a list of records that are all unique (per Product and Category). includes does eager loading which just loads the associated tables already but does an outer-join, and therefore, the retrieved records are also unique (but only per Product).
Product.joins(:categories).where(categories: { id: #my_product.categories.pluck(:id) } )

Related

Rails: chaining multiple filters together for has many through: relationships

I am in need of some help with chaining some filters involving has many through relationships.
Some background:
I have a model called product. A product has many categories. Vice versa, a category can have many products.
I have a model called colours, A product can have many colours. These 2 are also linked via has many through relationships.
My main goal is to somehow be able to filter items based on category and colour. I am receiving input from the user via a form.This would mean doing something in the controller like
#products= Product.includes(:categories).includes(:colours)
.where(categories: {id: params[:item][:category_ids]})
.where(colours: {id: params[:item][:colour_ids]})
However, this solution comes with a lot of problems apart from being real janky. Plus, if a user does not pass in any filters, it just filters with nils or "".
Is there a better way of chaining multiple has many through relationships like this? I've been trying to find any hints of how to do this online but I am still clueless on what to do here. Any help would be much appreciated. Also, I can edit this post if any additional code is needed.
Your implementation looks quite good). But i would suggest you this. If you just need filtering products then you can call left_joins and pass there your join tables(products_categories, products_colours) instead of target tables. Why? Because it will reduce LEFT JOIN clauses in your sql query and don't load objects into memory, hence you'll improve performance. But it only will work if you don't need to go through your products and take his categories or colours.
So query will look like.
Product
.left_joins(:products_categories, :products_colours)
.where(products_categories: { category_id: params[:item][:category_ids].presence || Category.ids } )
.where(products_colours: { colour_id: params[:item][:colour_ids].presence || Colour.ids }

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

Many to Many self reference ruby-on-rails

this is my first post in the community and a have a problema: represent a tree of categories.
First solution: each category has a reference to its parent category.
The problem with this solution is, when I want a subtree of categories I have to query the database several times, one query per level (height) of the tree and I want some solution more optimized.
Chosen solution: Category (id, name, description) and CategoryxCategory (ancestor_id, descendant_id)
And so to build a sub tree I'll need only one query like this ,
select * from category a, categoryxcategory b.where b.ancestor_id = 1 and
b.descendant_id = a.id , gets all sub-categories of the category with id = 1.
I've done this solution in java using hibernate, but I could not do in Rails, how do I specify this in the migration and ActiveRecord?
Many Thanks
Can't you just have ancestor_id and descendant_id be columns in category?
here's a popular gem that does trees for you https://github.com/collectiveidea/awesome_nested_set
there is another pattern: Nested Set, which is quite performant on read, but not if you update large trees / insert / reorder. take a look at the nested_set gem here. a self referencing many2many would be essentially a graph, which means, if you're not careful, you could end up with corrupt trees (i.e. nodes with several parents).

EF4 retrieve many to many with single call

Imagine a simple database where students have multiple courses and multiple exams, given a list of students already loaded from the db, I want to populate the lists of courses and exams with a single database call for each.
I know I can use Include up front which results in a single call to retrieve everything:
var students = context.Students.Include("Courses").Include("Exams").ToList();
but I also need to be able to load the courses and exams at a later time.
I tried:
var courses = students.SelectMany(x => x.Courses).ToList();
var exams = students.SelectMany(x => x.Exams).ToList();
but this resulted in two db calls for each student. How can I achieve this more efficiently?
From my experience, you cannot load multiple EntityCollections simultaneously. You provided the two options in your question. You can either eagerly load the related entities in a single query or you can load them later via a query for each EntityCollection to load.
Make sure you turn lazy loading off for your entity framework model, IIRC it'll cause those collection properties to fire queries whenever you iterate over them. Otherwise it looks like it should work with just one query.

Wrapping my head around MongoDB, mongomapper and joins

I'm new to MongoDB and I've used RDBMS for years.
Anyway, let's say I have the following collections:
Realtors
many :bookmarks
key :name
Houses
key :address, String
key :bathrooms, Integer
Properties
key :address, String
key :landtype, String
Bookmark
key :notes
I want a Realtor to be able to bookmark a House and/or a Property. Notice that Houses and Properties are stand-alone and have no idea about Realtors or Bookmarks. I want the Bookmark to be sort of like a "join table" in MySQL.
The Houses/Properties come from a different source so they can't be modified.
I would like to be able to do this in Rails:
r = Realtor.first
r.bookmarks would give me:
House1
House2
PropertyABC
PropertyOO1
etc...
There will be thousands of Houses and Properties.
I realize that this is what RDBMS were made for. But there are several reasons why I am using MongoDB so I would like to make this work.
Any suggestions on how to do something like this would be appreciated.
Thanks!
OK, first things first. You've structured your data as if this were an RDBMS. You've even run off and created a "join table" as if such a thing were useful in Mongo.
The short answer to your question is that you're probably going to have re-define "first" to load the given "Bookmarks". Either "server-side" with an $in clause or "client-side" with a big for loop.
So two Big Questions about the data:
If Bookmarks completely belong to a Realtor, why are they in their own collection?
If Realtors can Bookmark Houses and Property, then why are these in different collections? Isn't this needless complication? If you want something like Realtor.first on bookmarks why put them in different collections?
The Realtors collection should probably be composed of items that look like this:
{"name":"John", "bookmarks": [
{"h":"House1","notes":[{"Nice location","High Ask"}] },
{"p":"PropertyABC","notes":[{"Haunted"}] }
] }
Note how I've differentiated "h" and "p" for ID of the house and ID of the property? If you take my next suggestion you won't need even that.
Taking this one step further, you probably want Houses and Properties in the same collection, say "Locations". In the "Locations" collection, you're just going to stuff all Houses and Properties and mark them with "type":"house" or "type":"property". Then you'll index on the "type" field.
Why? Because now when you write the "first" method, your query is pretty easy. All you do is loop through "bookmarks" and grab the appropriate key ("House1", "PropertyABC") from the "Locations" collection. Paging is straight forward, you query for 10 items and then return.
I know that at some level it seems kind of lame."Why am I writing a for loop to grab data? I tried to stop doing that 15 years ago!" But Mongo is a "document-oriented" store, so it's optimized for loading individual documents. You're trying to load a bunch of documents, so you have to jump through this little hoop.
Fortunately, it's not all bad. Mongo is really fast at loading individual docs. Running a query to fetch 10 items at once is still going to be very quick.

Resources