Seeking help to prevent adding table with too many columns - ruby-on-rails
In my app I have a model called Post, that has a belongs to association with the Blog. That's how they look like:
app/models/post:
class Post < ApplicationRecord
belongs_to :blog
end
app/models/blog:
class Blog < ApplicationRecord
has_many :posts
end
The app will collect and store posts of a lot of content management systems (like Wordpress), and there are columns that should only exist for certain types of posts. I would like to know if it's possible to have some kinda of hierarchy, like this:
Post
- WordpressPost
- DrupalPost
This is important because that are columns that should only exist when it's a WordpressPost, for example.
I've tried to understand something called Polymorphic association (I really did), but I just can't get my head around the concept, and I'm also not sure if that's what's going to allow me that flexibility.
Right now the posts table look like this:
+------------+---------------------------------------------------------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------------------------------------------------------------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| site_id | bigint(20) | NO | MUL | NULL | |
| status | enum('auto_draft','draft','future','pending','private','publish','trash') | NO | | NULL | |
| date | datetime | NO | | NULL | |
| title | varchar(255) | NO | | NULL | |
| url | varchar(255) | NO | | NULL | |
| body | longtext | NO | | NULL | |
| created_at | datetime(6) | NO | | NULL | |
| updated_at | datetime(6) | NO | | NULL | |
+------------+---------------------------------------------------------------------------+------+-----+---------+----------------+
But things like:
- status
- date
Should only exist for Wordpress posts. I really would like to understand the best way to do what I want.
Thank you.
when mapping OO inheritance to relational database we traditionally have three ways of doing it (as highlighted in this link):
Single Table Inheritance(STI): One table with all the attributes used by all the subclasses. This way is more suitable for cases where the attributes don't change much from one subclass to another.
Class Table Inheritance(MTI): One dad table with common attributes, and child tables for each subclass with their specific attributes and a foreign key to the dad table. Good when there are many different attributes between each subclass and even different associations.
Concrete Table Inheritance: One table for each class with all the needed attributes. May be good if there are little common attributes.
Rails allow us to create a Single Table Inheritance(STI) and we can also use the Concrete Table Inheritance. But it doesn't have a built in way of dealing with the Class Table Inheritance(known as well as Multiple Table Inheritance - MTI), what doesn't prevents you from creating your own way of doing it, like it is done in this post.
Rails also allow us to have a Polymorphic Association, which is useful when we want an association of one type for certain records and of another for others, this can be used for inheritance, but was not actually designed for this. In this link you will find an example of all the three types and how to use them.
Another way to achieve it is by having an options jsonb field. This is a good approach when you have unpredictable/changeable fields that will vary from one record to another. Which is a much simpler way but must be well designed, cause may have some downsides.
Proposed Solution:
I wouldnt use:
STI: as you said it is going to be a lot of different management systems, I believe it is not a good idea because you would have too many unused columns.
Concrete Table Inheritance: I guess you want to make it easy to add new management systems as you go, treating them as one and ignoring their nuances. So it is not really a good option because it would make it harder to understand their commonalities.
I could use:
MTI: it would be a good database design, but as youre using rails you would have to deal with it your own way and it could add some complexity to the code. I would use it if I were more concerned about having a normalized database.
Polymorphic Association: would add some complexity to the database when compared to the MTI and to make it usable as an inheritance would add some complexity to the code as well. I prefer using it with an actual polymorphic association.
I would use:
An options json field: I believe you will need to access this specific attributes just in some specific moments and that each different management system wouldn't have other associations. So I guess it would be the most straightforward solution being totally supported by both rails and the database without adding complexity to neither one.
Ps: If you have just some little different attributes, I would use a STI instead.
As u need different set of columns for different post type based on the cms(post source),
you have to setup specific set of columns required by each post source.
setting up all the required columns in the same Post model itself is not recommended.
because scenarios like removing a post source or adding a post source into your application will be hard in future.
The solution I suggest is to setup a separate model for each post source like
class WordpressPost < ActiveRecord::Base
belongs_to :post
end
class JoomlaPost < ActiveRecord::Base
belongs_to :post
end
Each of these model will have their own specific set of columns like status and data for WordpressPost model and so on to hold the post data along with the post_id.
Then, the Post model can be updated like
class Post < ApplicationRecord
belongs_to :blog
has_one: wordpress_post
has_one: joomla_post
end
Additionally, in the posts table,
you have to include a column called 'post_source' which stores the integer values to denote the post source type.
the reference value for each integer value can be obtained using an helper function like
def get_post_source(source_val)
if (source_val == 1)
source_info = { name: 'Wordpress', model: 'WordpressPost' }
elsif (source_val == 2)
source_info = { name: 'Joomla', model: 'JoomlaPost' }
end
return source_info
end
Using this approach,
you can maintain scalability with ease.
If you have to remove joomla posts entirely from your application, then you can do that without affecting the structure of any other model.
Similarly, you can also add other new post sources like drupal, medium and so on by adding separate models for each.
I think there is another way to do that, changing the database. Like mongoDB, it's easier to meet your goal. If you dont't like mongoDB you can choose Postgres either, which can store hash like data structure.
Back to your polymorphic problem
class Person < ActiveRecord::Base
has_one :address, :as => :addressable
end
class Company < ActiveRecord::Base
has_one :address, :as => :addressable
end
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
end
In above example, address is the common part.
So you should define two classes
Post
- WordpressPost
- Content
- DrupalPost
- Content
From the answer from Rafael, you could use the singl etable inheritance.
first, allow null data in the status and date columns (you need to create a migration for that), and even create a new column that stores the content management system so later you could do a validation before saving (ensuring that the column exist in the cases that are needed) or to make querys only on some of your content by cms.
Related
Rails using Views instead of Tables
I need to create a Rails app that will show/utilize our current CRM system data. The thing is - I could just take Rails and use current DB as backend, but the table names and column names are the exact opposite Rails use. Table names: +-------------+----------------+--------------+ | Resource | Expected table | Actual table | +-------------+----------------+--------------+ | Invoice | invoices | Invoice | | InvoiceItem | invoice_items | InvItem | +-------------+----------------+--------------+ Column names: +-------------+-----------------+---------------+ | Property | Expected column | Actual column | +-------------+-----------------+---------------+ | ID | id | IniId | | Invoice ID | invoice_id | IniInvId | +-------------+-----------------+---------------+ I figured I could use Views to: Normalize all table names Normalize all column names Make it possible to not use column aliases Make it possible to use scaffolding But there's a big but: Doing it on a database level, Rails will probably not be able to build SQL properly App will probably be read-only, unless I don't use Views and create a different DB instead and sync them eventually Those disadvantages are probably even worse when you compare it to just plain aliasing. And so I ask - is Rails able to somehow transparently know the id column is in fact id, but is InvId in the database and vice versa? I'm talking about complete abstraction - simple aliases just don't cut it when using joins etc. as you still need to use the actual DB name.
relating has_and_belongs_to_many table to a fourth table
Thanks for the help first of all. I have User(table) and has_and_belongs_to_many Products(table) with a joining table UserJoinProducts. I have this and works. My problem is.. I want to create a table where Users have a records when they CheckIn or Out a Product and also have record of each Iteration. I want user to have a record for each time they check_in or out the same product. |user|produ| _date-- |in | |bob | eggs |1/1/2016| X | |bob | Coke |1/1/2016| X | |bob | eggs |1/5/2016| -- | |bob | Coke |1/7/2016| -- | |bob | eggs |1/9/2016| X | Click here for A sad example of my table ;) lol Thanks Again For your Help.
I wouldn't do it this way. You're probably better off using has_many through relation for User and Product models. And then use the through table to store the check_in or check_out datetime fields. So this way you'll have a model for your joining table to access these fields. It's always recommended to use has_many through as whenever you use has_and_belongs_to_many, most of the time you end up in a similar situation, and would want to change the architecture.
rails user-defined custom columns
I am using Ruby on Rails 4 and MySQL. I have three types. One is Biology, one is Chemistry, and another is Physics. Each type has unique fields. So I created three tables in database, each with unique column names. However, the unique column names may not be known before hand. It will be required for the user to create the column names associated with each type. I don't want to create a serialized hash, because that can become messy. I notice some other systems enable users to create user-defined columns named like column1, column2, etc. How can I achieve these custom columns in Ruby on Rails and MySQL and still maintain all the ActiveRecord capabilities, e.g. validation, etc?
Well you don't have much options, your best solution is using NO SQL database (at least for those classes). Lets see how can you work around using SQL. You can have a base Course model with a has_many :attributes association. In which a attribute is just a combination of a key and a value. # attributes table | id | key | value | | 10 | "column1" | "value" | | 11 | "column1" | "value" | | 12 | "column1" | "value" | Its going to be difficult to determin datatypes and queries covering multiple attributes at the same time.
Dynamic categories criteria with matching products rails 3
I'm working on a new rails project, and in this project I have products and product categories. So these categories are very different from each other, to name some, Boats, Houses, Cars. The car category might have criterias like "Mph", "Model", "Brand", "Year" and so on. Where the house category will have something like "Rooms", "Year", "City", "Postal Code" etc. I would like this to be very dynamic, so that i would be able to add/remove criterias and add/remove categories from a backend panel. Now to my question, i have been playing around with this, and i can't really figure out the logic of this concept, i have tried some solutions, however they are very weird and quite inefficient. Maybe some hardcore rails coder could give me a hint, on how to solve this puzzle? So the best solution i could come up with, was this: Four models : _______________________ | Product.rb | ----------------------- | id | integer | ----------------------- | category_id | integer | ----------------------- | Title | string | ----------------------- | Description | text | ----------------------- _______________________ | Category.rb | ----------------------- | id | integer | ----------------------- | Title | string | ----------------------- | Description | text | ----------------------- _______________________ | Criteria.rb | ----------------------- | id | integer | ----------------------- | category_id | integer | ----------------------- | Name | string | ----------------------- | Default | string | ----------------------- | Description | text | ----------------------- _______________________ | ProductInfo.rb | ----------------------- | id | integer | ----------------------- | product_id | integer | ----------------------- | Name | string | ----------------------- | Value | text | ----------------------- How it's connected : Criteria.rb is connected to Category.rb with a category_id and has_many/belongs_to relation Product.rb is connected to Category.rb with a category_id and has_many/belongs_to relation ProductInfo.rb is connected to Product.rb with a product_id and has_many/belongs_to relation. Category.rb is the heart og this solution. The category model, both have many products and criterias. How it should work, in reality : In the show category page, i would first print out all the criterias for the given category. Afterwards i would make a #products.each do |product|. In the #products.each block, i would make a #category.criterias.each do |criteria|. In the #category.criterias.each block, i would then run something like product.productinfos.where(:name => criteria.name). And then run it one by one. Conclusion, this solution do work, however i doubt that it is the best solution. It will make an extremely big loadtime, with high traffic and many data. And i will need to write very weird and unreadable code. This is a rather long question, and it might be very confusing, so if there is anything please just say. Also, i have searched quite alot for a question like this, both on Stackoverflow, and on google but i have not been able to find anything like this. Oluf Nielsen.
In my opinion, it's better not to define additional tables to handle this due lots of performance issues. My preference to handle such things is to use a serialized column in the products table. Ability to search directly in the database is reduced with this approach, but then you wouldn't want to do that anyway. To handle search, you have to add some sort of indexed searching mechanism. Like acts_as_ferret or even Solr or ElasticSearch. If you are using postgres check out https://github.com/softa/activerecord-postgres-hstore For Mysql, use the rails's built in 'store' http://api.rubyonrails.org/classes/ActiveRecord/Store.html class Product < ActiveRecord::Base has_and_belongs_to_many :categories store :settings end To set criteria for each category do something similar to this: class Category < ActiveRecord::Base has_and_belongs_to_many :products def criteria #criteria_list ||= self[:criteria].split('|') #criteria_list end def criteria=(names) self[:criteria] = names.join('|') end end Everytime a product is added to a category, check if all of the criteria in that category is available in the product's properties hash keys. If not, add it with a default value if needed. You can also setup accessors for the properties hash store using a proc that dynamically gets the accessor names from the all the criteria field of the categories of the product? (not sure about this, cause I haven't done this before) You can also look into using STI (Single table Inheritance) using a type field in your products table. (It's well documented) This approach is slightly better 'cause when products move from one category to another, the properties won't change. class Gadget < Product store_accessor :manufacturer, :model end class Phone < Gadget store_accessor :os, :touch_screen, :is_smart end Hope this helps
Or else, second approach would be go with a nosql database. Try mogodb with mongoid, which is quite stable. This will suite your requirements for variable attributes very well. Also you can add any other categories later with very ease. As far as I can see, with mysql, you will end up creating multiple dbs for storing this dynamics data and that is bound hamper the performance. UPDATE - Apart from the point that your data can be flexible with nosql, there are many things you need to consider before shifting there. I just suggested based on fact that you need flexible database structure. Start with mogodb docs, they are good starting point.
Database design for multi level users with rails
I have a database table for users which contains common fields like password, email etc. And there is also a field named level which defines user level such as member, editor or admin. There is also some fields that specific for members, editors and admins which others don't need. So I think I should create separated tables for user types. And here's the problem; how should I approach this problem if I want to follow Rails way? Both in terms of database design and associations.
seems you are looking for a role-based authorization system, coupled with specific attributes for each role. One way to achieve this would be with a data model looking like this : .-------.1 *.------------.* 1.-------. | users |<-----| user_roles |------>| roles | '-------' '------------' '-------' | .---------------+---------------------. |0..1 |0..1 |0..1 .--------. .----------------------. .----------. | points | | some_other_attribute | | room_ids | '--------' '----------------------' '----------' this way, you ensure that all attributes related to a specific role are deleted if that role is removed from the user (by cascade delete). You will have, though, to ensure that all your attributes models enforce validation rules like this : class Point < ActiveRecord:Base validates :relevant_association? def relevant_association? user_role.role.title == "Admin" end end if your user can only have one role, you can simplify this by adding a role field on the model, and then write validation rules on optional attributes (that belong_to a user) accordingly. Still,the former model offers more potential for future adjustments (creating a new role is just creating a new record). I'm not an expert on this matter though, so you can also continue to seek out inspiration ; the declarative_authorization gem provides an explanation of its data model that you may find interesting, too: includes includes .--. .---. | v | v .------. can_play .------. has_permission .------------. requires .----------. | User |----------->| Role |----------------->| Permission |<-----------| Activity | '------' * * '------' * * '------------' 1 * '----------' | .-------+------. 1 / | 1 \ * .-----------. .---------. .-----------. | Privilege | | Context | | Attribute | '-----------' '---------' '-----------'