How to model the database for a backpack-like application - ruby-on-rails

I would like to create an application that would use the same system as backpack (http://www.backpackit.com) to create different types of pages.
Basically, you can add different elements to a page, and reorder them. Some elements can contains other elements (like an image gallery which contains... images, or lists etc).
I'm not sure how to model that.
I'd like to be able to do something like:
page.elements
without having to retrieve all elements myself
class Page < ActiveRecord::Base
has_many :texts, :dependent => :destroy
has_many :titles, :dependent => :destroy
def elements
#elts = texts + titles + ...
#order elts...
end
end
So I was thinking about single table inheritance.
I could have a Containers table, and Notes, Galleries, Lists etc could inherit from Containers.
And then, I would have Elements that could be linked to various Containers using polymorphism.
How would you do that? Do you see any fundamental flaws in my approach?
Thanks!

First off, the design is not as efficient as it could be, but whether or not it is fundamentally flawed actually depends on your level of experience:
Case 1: You are relatively new to programming and trying to get started by reverse-engineering and implementing something you can see and understand (backpackit). If this is true then you cannot go wrong by diving in and using the ORM philosophy that database tables can be designed as if they were persisting classes. It will be inefficient, but you'll learn plenty by not having to worry about the database -- yet.
Case 2: You are a veteran programmer (at least one decent app actually being used by people who paid for it) and for some reason are still expressing database design questions in object-oriented terminology. Then you have a fundamental flaw only because there is a good chance you will experience success that will stress the system, at which point the fundamental inefficiency of "table inheritance" will bite you.

Related

Database modeling best practice: Dealing with Associations to the same "base"

I tried to normalize my database model. But I'm clueless how to do it in this case. Giving the following model:
Customer has many Systems (has_many :systems)
Cluster has many Systems (has_and_belongs_to_many :systems)
I want to display all Systems of a Customer. That would be: #customer.systemobjects.each. That is already working.
Then I could add a System to a Cluster (which I mentioned is a "HABTM" association). In my Customer view I want to show only systems, that are not related to a cluster (also working with Cluster.includes(:systems).where(systems: { id: sysid }).present?).
Now my question: I want to display all Clusters (and Systems of that Cluster) of a specific customer, too. But, right now, I only to have the connection to customer through systems. For me, it would be easier, to add a reference to customer also in the cluster object (even though I would have this information already in the system).
Should I add this reference? Does it have something to do with normalization anyway? How would you assess this situation from a best practice point of view for a databases in general and for Ruby On Rails in specific? How would also be the best way, to go through each cluster of a customer, when I have it only through systems (how I could do it in rails?)?
I think you'd prefer something like this:
class Customer
has_many :systems
has_many :clusters, through :systems # expect `clusters_id` in System, which is typical
# ...other code
end
class System
belongs_to :customer
belongs_to :cluster
# ...other code
end
class Cluster
has_many :systems
has_many :customers, through :systems # expect `customer_id` in System, which is typical
# ...other code
end
This results in three tables, as your model already implies, but uses the systems table as a "hinge" lookup table for the other two without implication that clusters belong to systems (which doesn't make sense IRL as I understand your problem statement).
I often find has_many :through is often an easier and better choice than HABTM unless you truly have a mutual belonging relationship. You can go to the Rails Guides here and here (guide: "Active Record Associations") for more information on use :through. Definitely worth getting to know that guide for the kind of questions you have (though to be fair, it can take a bit of experience to fully appreciate the various options and how they're helpful).
Now when you want to refer to clusters that a customer has systems within, you merely need to write something like this:
my_customer = Customer.find(some_id)
customer_clusters = customer.clusters
customer_systems = customer.systems
To find all the customers for a cluster (and so through clusters' systems), you'd write something like this:
target_cluster = Cluster.find(some_id)
cluster_customers = target_cluster.customers
If you want a want to produce a hierarchy of those (say, systems of a customer grouped under the clusters they belong to), it'd be something like this:
my_customer = Customer.find(some_id)
customers_systems = customer.systems.includes(:cluster) # this brings in the associated cluster records without additional database operations
then either iterate or use group_by from the resulting data in customer_systems depending on how you intend to display or return the data.

Small number of set categories with many-to-many relation?

I'm relatively new to the Rails framework and I'm not sure if the approach I am taking is the most efficient/effective way or if I am following Rails conventions well.
The basic issue I have is that my application will have a Company model and various set Categories (not editable by the user). Each Company can be part of multiple Categories. My understanding, from other examples, is that I should set the relationships as something like:
Company has_many_belongs_to_many Categories
Category has_many_belongs_to_many Companies
However, since there will not be that many categories (<10), and since they will not change/be editable/be added/be removed by users, I'm not sure I need to create a whole new table for categories then join them onto Companies? Is there a better way to do this in Rails that I'm missing? Thanks in advance!
Even though you may only have 10 categories or so, I would say this is still fine to have it in its own table. Setting up the relationships give you programmatic power to retrieve companies related to a single category and vice versa when you need it without having to reconstruct queries yourself.
An example of the simplicity for keeping those in the database:
# Get all companies under a specific Category
#category = Category.find(1)
#companies = #category.companies
That's pretty simple if you ask me. And if you add another category to the table in the future, you won't need to write any new code to get it work.
Another thing, I would check out using has_many :through instead of has_and_belongs_to_many (habtm), as habtm can cause unforeseen problems as your application gets bigger. Here is a great article that goes into that problem a lot deeper: Why You Don’t Need Has_and_belongs_to_many Relationships. Not saying you can't use it (if the shoe fits), but generally it's good to be aware of potential problems so you can make the right decision for you and your app.

How best to model reservation/appointments database in Rails

I'm looking to write some sort of Rails app to help users book time slots at a restaurant. How can this be modeled in such a way so that it can be reservations can be displayed and booked through the browser? I have two models:
class Restaurant < ActiveRecord::Base
has_many :reservations
class Reservation < ActiveRecord::Base
belongs_to :restaurant
attr_accessible :name, :date, :time
At first I toyed with a using a hash within the Restaurant model to store availability, using dates as keys. But then I realized that Rails databases must serialize hashes, and I'd like to make sure there isn't a better way to go about this before I dive into that stuff.
I'm using Postgres (if that's relevant). Any help would be much appreciated!
Your basic model structure is fine. Note that attr_accessible is not current best practices, as of Rails 4. (It has been replaced by strong parameters)
Doesn't matter too much about what database you are using (even though PG is a solid choice) but the general engineering that is truly important.
I won't give you a copy paste answer but hopefully give you some direction.
So a Restaurant can have many Reservations throughout the day. I assume each restaurant can only hold so many people and thus have some type of "reservation limit" and Reservations cannot overlap.
Because of the constraints I imagine your 2 model method will work. You just need to figure out exactly how they must interact to work as you plan.
Restaurants should be able to keep track of open times/vacancies (or whatever important details). While Reservations will keep track of the number of people in the party, time, etc.
Your initial relationship looks to be well defined. But the other answer does correctly point out the new Rails 4 preferred method.

Ruby on Rails - Alternatives to STI?

I have many different models (close to 20) that share some common attributes but also differ to some degree in others. STI seems attractive at first, but I have no idea how the various models will evolve over time with rapid product development.
A good parallel to our application that comes to mind is Yelp. How would Yelp manage something in Rails? All of the postings have some common attributes like "address". Yet, they differ quite a lot on others. For example, you have a reservation option for restaurants and maybe not for others. Restaurants also have a ton of other attributes like "Alcohol allowed" that don't apply to others. Doing this with STI will get out of hand pretty quickly.
So whats the next best option? HStore with Postgres? I am not comfortable using HStore for anything but small things. HStore solves some problems while introduces others like lack of data types, lack of referential integrity checks etc. I'd like a solid relational database as the foundation to build upon. So in the Yelp case, probably, a restaurant model is where I am going. I've taken a look at suggestions like here - http://mediumexposure.com/multiple-table-inheritance-active-record/, but I am not happy to do so much monkey patching to get something so common going.
So I am wondering what other alternatives exist (if any) or should I just bite the bullet, grind my teeth and copy those common attributes into the 20 models? I am thinking my problems would come from the migration files rather than the code itself. For example, if I setup my migrations to loop through tables and set those attributes on the tables, then would I have mitigated the extent of the problem with having different models?
Am I overlooking something critical that might cause a ton of problems down the road with a separate models?
I see a few options here:
Bite the bullet and create your 20 different models with a lot of the same attributes. It's possible that these models will drift over time - adding new fields to one specific type - and you'll create a 200 column table with STI. Maybe you don't - the future is hard to see, especially with exploratory/agile software.
Store non referential fields in a NoSQL (document) database. Use your relational database for parts of the record that are relational (a user has many reviews and a review has one business), but keep the type specific stuff in a NoSQL database. Keep an external_document_id in your Rails models and external_record_id / external_record_type in your NoSQL document schema so you can still query all bars that allow smoking using whatever NoSQL ORM you end up using.
Create an Attributes model. An attribute belongs_to :parent_object, polymorphic: true with a key and value field. With this approach you might have a base Business model and each business can has_many :attributes. Certain (non-relational?) attributes of the business (allows_smoking) are one Attribute record. An Attribute's key could be a string or could be a numeral you have Ruby constants for. You're essentially using the Attribute entities to create a SQL version of option #2. It might be a good option, and I've used this myself for User or Profile models. (Although there are some performance hits to be aware of with this approach).
I'd really worry about having that many (independent) models for something that sounds subclass-ey. It's possible you might be able to DRY up common behavior/methods by using Concerns (syntactic sugar over the mixin concept, see an awesome SO answer on concerns in Rails 4). You still have your (initial) migration problem, of course.
Adding another option here: Serialized LOB (272). ActiveRecord allows you to do this to an object using serialize:
class User < ActiveRecord::Base
serialize :preferences
end
user = User.create(preferences: { "background" => "black", "display" => large })
User.find(user.id).preferences # => { "background" => "black", "display" => large }
(Example code from ActiveRecord::Base docs.)
The important consequence to understand is that attributes stored in a Serialized LOB will not be indexable and certainly not searchable in any performant manner. If you later discover that a column needs to be available as an index you'll have to write [most likely] a Ruby program to perform the transformation (though by default serialization is in Yaml so any Yaml parser will suffice).
The advantage is that you don't have to make any technology changes to your stack in order to apply this pattern. Its easy to moderate - based on the amount of data you have collected - to migrate away from this pattern.

Best way to handle multiple tables to replace one big table in Rails? (e.g. 'todo_items1', 'todo_items2', etc., instead of just 'todo_items')?

Update:
Originally, this post was using Books as the example entity, with
Books1, Books2, etc. being the
separated table. I think this was a
bit confusing, so I've changed the
example entity to be "private
todo_items created by a particular
user."
This kind of makes Horace and Ryan's original comments seem a bit off, and
I apologize for that. Please know that
their points were valid when it looked
like I was dealing with books.
Hello,
I've decided to use multiple tables for an entity (e.g. todo_items1, todo_items2, todo_items3, etc.), instead of just one main table which could end up having a lot of rows (e.g. just todo_items). I'm doing this to try and to avoid a potential future performance drop that could come with having too many rows in one table.
With that, I'm looking for a good way to handle this in Rails, mainly by trying to avoid loading a bunch of unused associations for each User object. I'm guessing that other have done something similar, so there's probably a few good tips/recommendations out there.
(I know that I could use a partition for this, but, for now, I've decided to go the 'multiple tables' route.)
Each user has their todo_items placed into a specific table. The actual "todo items" table is chosen when the user is created, and all of their todo_items go into the same table. The data in their todo items collection is private, so when it comes time to process a users todo_items, I'll only have to look at one table.
One thing I don't particularly want to have is a bunch of unused associations in the User class. Right now, it looks like I'd have to do the following:
class User < ActiveRecord::Base
has_many :todo_items1, :todo_items2, :todo_items3, :todo_items4, :todo_items5
end
class todo_items1 < ActiveRecord::Base
belongs_to :user
end
class todo_items2 < ActiveRecord::Base
belongs_to :user
end
class todo_items3 < ActiveRecord::Base
belongs_to :user
end
The thing is, for each individual user, only one of the "todo items" tables would be usable/applicable/accessible since all of a user's todo_items are stored in the same table. This means only one of the associations would be in use at any time and all of the other has_many :todo_itemsX associations that were loaded would be a waste.
For example, with a user.id of 2, I'd only need todo_items3.find_by_text('search_word'), but the way I'm thinking of setting this up, I'd still have access to todo_items1, todo_items2, todo_items4 and todo_items5.
I'm thinking that these "extra associations" adds extra overhead and makes each User object's size in memory much bigger than it has to be. Also, there's a bunch of stuff that Ruby/Rails is doing in the background which may cause other performance problems.
I'm also guessing that there could be some additional method call/lookup overhead for each User object, since it has to load all of those associations, which in turn creates all of those nice, dynamic model accessor methods like User.find_by_something.
I don't really know Ruby/Rails does internally with all of those has_many associations though, so maybe it's not so bad. But right now I'm thinking that it's really wasteful, and that there may just be a better, more efficient way of doing this.
So, a few questions:
1) Is there's some sort of special Ruby/Rails methodology that could be applied to this 'multiple tables to represent one entity' scheme? Are there any 'best practices' for this?
2) Is it really bad to have so many unused has_many associations for each object? Is there a better way to do this?
3) Does anyone have any advice on how to abstract the fact that there's multiple "todo items" tables behind a single todo_items model/class? For example, so I can call todo_items.find_by_text('search_phrase') instead of todo_items3.find_by_text('search_phrase').
Thank you!
This is not the way to scale.
It would probably be better going with master-slave replication and proper indexing (besides primary key) on fields such as "title" and/or "author" if that's what you're going to be looking up books based on. Having it in n-tables, how are you going to know the best place to go looking for the book the user is after? Are you going to go looking through 4 tables?
I agree with Horace: " don't try to solve a performance issue before you have figures to prove it." I suggest, however, that you should really look into adding indexes to your table if you want lookups to be fast. If they aren't fast, then tell us how they aren't fast and we will tell you how to make it go ZOOOOOM.

Resources