Rails: Multiple trees for a single item - ruby-on-rails

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?

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 }

Perform a join on two Rails models implementing single table inheritence

I have three models, let's call them Product, TemplateProduct and ReadyProduct. There is only one table for these, the products table, and both TemplateProduct and ReadyProduct inherit from the Product model. There is a has_many/belongs_to association between TemplateProduct and ReadyProduct. Templates are used to lay out general characteristics for products, Readys are used to customize the products and what are actually made available for view by the customer. Each TemplateProduct has an id and each ReadyProduct has a template_product_id which ties to it's template.
The project is built using Rails 5.
What I want to be able to do is to gather a list of TemplateProducts, then get a count of each templates associated ReadyProducts and do so in such a way that won't hammer the database. I understand ActiveRecord associations but my SQL is weak and I have only a limited understanding of joins. I can gather a list of TemplateProducts with a simple Product.where(conditions) but I don't know what to once I have this. For the sake of flexability sake I want to be able to base my ReadyProduct count off of this initial collection as sometimes I'll need the additional count and sometimes I won't. I'm sure there must be a simple way to do this but I haven't found a solution.
If you just need a mapping of TemplateProduct ids to ReadyProduct counts, then all you need is:
TemplateProduct.joins(:ready_products).group(:id).count
If you want TemplateProduct instances with a baked in ReadyProduct count, then you'll need this instead:
tps = TemplateProduct.joins(:ready_products).select('products.*, COUNT(ready_products_products.id) ready_product_count').group(:id)
tps.first.ready_product_count
#=> 6
ready_products_products gets defined by Rails, it prefixes the actual table name (products) with the model name's "table form" (pluralized, snake case, lower case, ready_products), joined with an underscore.

Tables for orders

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.

Can I have a one way HABTM relationship?

Say I have the model Item which has one Foo and many Bars.
Foo and Bar can be used as parameters when searching for Items and so Items can be searched like so:
www.example.com/search?foo=foovalue&bar[]=barvalue1&bar[]=barvalue2
I need to generate a Query object that is able to save these search parameters. I need the following relationships:
Query needs to access one Foo and many Bars.
One Foo can be accessed by many different Queries.
One Bar can be accessed by many different Queries.
Neither Bar nor Foo need to know anything about Query.
I have this relationship set up currently like so:
class Query < ActiveRecord::Base
belongs_to :foo
has_and_belongs_to_many :bars
...
end
Query also has a method which returns a hash like this: { foo: 'foovalue', bars: [ 'barvalue1', 'barvalue2' } which easily allows me to pass these values into a url helper and generate the search query.
This all works fine.
My question is whether this is the best way to set up this relationship. I haven't seen any other examples of one-way HABTM relationships so I think I may be doing something wrong here.
Is this an acceptable use of HABTM?
Functionally yes, but semantically no. Using HABTM in a "one-sided" fashion will achieve exactly what you want. The name HABTM does unfortunately insinuate a reciprocal relationship that isn't always the case. Similarly, belongs_to :foo makes little intuitive sense here.
Don't get caught up in the semantics of HABTM and the other association, instead just consider where your IDs need to sit in order to query the data appropriately and efficiently. Remember, efficiency considerations should above all account for your productivity.
I'll take the liberty to create a more concrete example than your foos and bars... say we have an engine that allows us to query whether certain ducks are present in a given pond, and we want to keep track of these queries.
Possibilities
You have three choices for storing the ducks in your Query records:
Join table
Native array of duck ids
Serialized array of duck ids
You've answered the join table use case yourself, and if it's true that "neither [Duck] nor [Pond] need to know anything about Query", using one-sided associations should cause you no problems. All you need to do is create a ducks_queries table and ActiveRecord will provide the rest. You could even opt to use has_many :through relationship if you need to do anything fancy.
At times arrays are more convenient than using join tables. You could store the data as a serialized integer array and add handlers for accessing the data similar to the following:
class Query
serialize :duck_ids
def ducks
transaction do
Duck.where(id: duck_ids)
end
end
end
If you have native array support in your database, you can do the same from within your DB. similar.
With Postgres' native array support, you could make a query as follows:
SELECT * FROM ducks WHERE id=ANY(
(SELECT duck_ids FROM queries WHERE id=1 LIMIT 1)::int[]
)
You can play with the above example on SQL Fiddle
Trade Offs
Join table:
Pros: Convention over configuration; You get all the Rails goodies (e.g. query.bars, query.bars=, query.bars.where()) out of the box
Cons: You've added complexity to your data layer (i.e. another table, more dense queries); makes little intuitive sense
Native array:
Pros: Semantically nice; you get all the DB's array-related goodies out of the box; potentially more performant
Cons: You'll have to roll your own Ruby/SQL or use an ActiveRecord extension such as postgres_ext; not DB agnostic; goodbye Rails goodies
Serialized array:
Pros: Semantically nice; DB agnostic
Cons: You'll have to roll your own Ruby; you'll loose the ability to make certain queries directly through your DB; serialization is icky; goodbye Rails goodies
At the end of the day, your use case makes all the difference. That aside, I'd say you should stick with your "one-sided" HABTM implementation: you'll lose a lot of Rails-given gifts otherwise.

Chosing categories rails

Hopefully we have good rails developer who can definitely give correct answer! For 2 days I didn't receive any valid answer for my question
I will explain in a very simple example
Customer is offering product. When he pushes create it gives form. Choose a category. Once he chooses another form will pop up.
Depending on a category, form should have totally different attributes.I can't have Product.new for every category. Reason is they have different attributes(Logicaly true). So do I have to create 100 models for 100 categories
Categories are : cars, apartments, coupons, books and many more
If you can give just one example I will be gratefull and call you expert
Thanks
It sounds like you're getting there. However, I wouldn't have a bunch of models like you're indicating in your question. I would say that you need a Product model and a Category model. The Category model will belong_to Product. The Product model would have many Categories. The Category model can use the acts_as_tree gem so that you can have categories and subcategories. Use javascript or jQuery (there was a recent Railscasts on this) to dynamically change and post a different field with a set of choices based on what was chosen.
EDIT:
I would have three Models; Product, Category, Specification
Product has many Categories
Product has many Specifications through Categories
Category belongs to Product
Category has many Specifications
Specification belongs to Category
This way I can create a product that has several categories. I can create several categories that have several specifications. Specifications are linked to the respective category. This will allow you to have three models and limited number of classes. Once your project is complete, new categories and specifications can be maintained by a web admin instead of a programmer.
This isn't the answer you want, but you're going to need a lot of models.
The attributes associated with an apartment (square meters, utilities, floor of building) are completely different from the attributes associated with a car (make, model, mileage, condition) which are completely different from a book (title, author, publisher, edition, etc). These items are so fundamentally different that there is no way to manage them in a single model.
That being said, there may be a core collection of attributes that might be associated with a product that is for sale (seller, price, terms). You have basically two paths forward:
You could decide to use Single Table Inheritance. In this case, you'd create an abstract class that defines the attributes that are common to all products that you are selling (seller, price, item). You'd then add a "type" column to your database that would be used to determine what type of product it is (mapped to your categories), and define all of the possible attributes in a single table.
You could choose a core set of attributes, and use these as a part of any other object that is considered a product. You'd have multiple tables that would have the full record for any given object.
Without knowing a lot of details about your application, it's hard to make a specific recommendation about which approach is right for you. Your best bet at this point is to spend a lot of time on google with "single table inheritance rails" and "multi table inheritance rails" and figure out which one is right for you (though my gut says multi table).

Resources