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).
Related
I'm working on a project built on Rails 4, ActiveRecord and PostgreSQL and faced with a performance dilemma -
For brevity, let's say I have Category & Item models. Category has_many items.
Let's take the example where category 'Furniture' has 'bed, large mattress, small mattress, armchair', etc. While displaying these items under the category, we would intuitively want to see all kinds of mattresses and bed frames together, instead of being lexicographically ordered. Also, let's assume the total number of items under any category is in the order of < 100 (mostly about ~10-15 per category) & so naturally, the order of items falling in the same 'group' under a category would be much lower than that.
To achieve this grouping, one way is to create a SubCategory model and associate items through them, so we can add items of a certain group later on and still be able to show them together by grouping on the category & sub category.
The other way I'm thinking of, since the order of total items is so small, is to add an order (float type) field to the Item model to still be able to group them together (Bed = 5.01, Mattress = 5.02, Chair = 6.01, Bed Cover = 5.03 & so on).
The only reason I'm considering the other option is because we're confident on the number of items to not go beyond even a 100 in our application's scope and so the Sub Category route - creating a new model and persisting many columns vs one - seems like an overkill for this particular case.
So my question (finally!) is this -
What kind of pitfalls might I fall if I went the second route? Moreover, is sorting on a float field with Postgres an overall better tradeoff on speed and memory vs adding a new model to simulate sub groupings such as mentioned in the above example?
I'm having a very difficult time setting up model/table relationships for a hobby project I'm working on. What I have is an order, the order will have one or many packages, each package has one or many products, each product will have one area it is assigned to. There can be multiple of the same product on the order and in the same package but each will have a different area
and a different quantity. I'm struggling to determine how the relationships are set up.
At the end of the day I need to run a report that shows me the order details listing all packages on the order and all the products and areas contained in each package. I also need a report that shows me the order and a sum of all the quantities for each product on the order (no packages on this one). I'm using Rails ActiveRecord I think believe there will be a couple polymorphic relationships, but I'm struggling to identify them as this is outside of my simple "everything has a one-to-many relationship" thinking.
How can I put these tables together in an intelligent manner? I have searched for similar schema diagrams without much success.
This is the basics of what I am thinking of for relationships, but I'm not sure what the Rails models will look like. It seems like every relationship from orders down is polymorphic; how do I nest these relationships in Rails?
I think you need some JOIN tables.
An Order can have many Products; a Product can be added to many Orders.
In that case you'd have a JOIN table that would have two columns: primary keys for Order and Product. The two together would be a composite primary key.
I don't know what Area means in your schema, but perhaps this suggestion will break the mental logjam for you.
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) } )
I want to categorize objects in multiple trees to reflect their characteristics and to build a navigation on.
So, given the following trees:
Category1
-Category-1-1
-Category-1-2
Category2
-Category-2-1
-Category-2-2
--Category-2-2-1
An object could e.g. belong to both Category-1-2 and to Category-2-2-1.
The goal is to be able to fetch all objects from the database
that belong to a certain category
that belong to a certain category or its decendants
A more practical example:
A category might have a hierarchy of 'Tools > Gardening Tools > Cutters'.
A second category: 'Hard objects > Metal objects > Small metal objects'
An object 'Pruners' would be categorized as belonging to 'Cutters' as well as 'Small metal objects'.
I want to be able to
retrieve all 'Gardening Tools' -> 'Pruners'
retrieve all Category children of 'Gardening Tools' -> 'Cutters'
retrieve all 'Hard objects' -> 'Pruners'
retrieve all 'Hard objects' that are also 'Cutters' -> 'Pruners'
retrieve all 'Soft objects' that are also 'Cutters' -> []
Any pointers? I have briefly looked at closure_tree, awesome_nested_sets etc., but I am not sure they are a good match.
Please note that the code here is all pseudo code.
I would use ancestry gem and would model your data with three model classes.
This way your data is normalized and it's a good base to build on.
Category - ancestry tree
has_may Memberships
has_may Products through Memberships
Membership
belongs_to Category
belongs_to Products
Products
has_may Memberships
has_may Categories through Memberships
From there on you need to figure out how to perform the equerries efficiently.
My way of doing this is to understand how to do it with SQL and then figure out how to express the queries with activercord's DSL.
Some resources:
http://railsantipatterns.com/ This book has some examples of complex SQL queries turned into reusable scopes and helpers
http://guides.rubyonrails.org/active_record_querying.html#joining-tables Rails's documentation, see section on joins and includes
http://stackoverflow.com/questions/38549/difference-between-inner-and-outer-join A great explanation of SQL joins
Queries examples:
Find a category.
Category.find(category_id)
Find a category and include it's products inside the specified category.
Category.find(category_id).join(:memberships => :products)
Find a category's sub-tree ind include products
Category.subtree_of(category_id).join(:memberships => :products)
Find all categories a products belongs to.
Product.find(product_id).categories
I just did this and I chose not to use ancestry, but closure_tree because the author says it is faster and I agree with him. Know you need a `has_and_belongs_to_many' between Categories (which I like to call tags whenever I add multiple to a single object) and Objects.
Now the finders, the bad news is that without your own custom query you might not be able to do it with one. Using the gems methods you will do something like:
Item.joins(:tags).where(tags: {id: self_and_descendant_ids })
The code is clean and it executes two queries, one for the descendant_ids and another one in Objects. Slight variations of this, should give you what you need for all except the last. That one is tough and I haven't implemented it (I'm in the process).
For now, you will have to call tag.self_and_ancestor_ids on both (Query count: 2), all items in those tags (Query count: 4) and intersect. After this, some serious refactoring is needed. I think we need to write SQL to reduce the number of queries, I don't think Rails query interface will be enough.
Another reason I chose *closure_tree* was the use of parent_id, all siblings share it (just like any other Rails association) so it made it easier to interface with other gems (for example RankedModel to sort).
I think you could go for one of the tree gems, personally I like Ancestry. Then make an association for each category to have many objects and each object can belong to many categories.
Have you stumbled on any problems already or are you just researching your options?
I need to do a left outer join in rails, but I need the model objects to be for the joined table.
What I want is a list of the days, with the metrics for each day. I need to have all days regardless of whether or not there were metrics, but I don't want to make a bunch of round trips to the database.
This works, but causes problems because it thinks I have PeriodDay objects when I really want Metric objects:
PeriodDay.select("metrics.*").join('LEFT OUTER JOIN metrics ON period_days.date = metrics.date').where('period_id = ?', current_period)
I can use find_by_sql on the Metric object, but the query building is more complicated (and conditional) than this simplified example, so I would rather figure out the "rails way" for this problem.
My current workaround is to loop through the records and create Metric objects from the attributes of the PeriodDay object. It doesn't feel efficient, but it is better than making multiple database calls.
metrics = []
recs = PeriodDay.select("metrics.*").join('LEFT OUTER JOIN metrics ON period_days.date = metrics.date').where('period_id = ?', current_period)
for rec in recs
metrics << Metric.new(rec.attributes)
end
Assuming that Period has many PeriodDay has many Metric, and that period_id is an attribute of your PeriodDay model, your workaround should be identical to something like this:
Metric.includes(:period_day).where(:period_day => {:period_id => #current_period})
This doesn't get you a list of days with their respective Metric objects as you mentioned in the original question, but it gets you a list of all Metric objects for a particular period. (unless I'm missing something...)
If you want a list of PeriodDay objects with their included Metric objects, you can use includes instead of joins.
PeriodDay.includes(:metrics).where(:period_id => #current_period)
This will execute two queries (one to get period days and the other to get metrics) but it is a lot more readable.