Say we have 3 different applications - serviceapp, subscriptionapp, ecomapp, all written in ruby on rails and uses the same database in backend and tables in the backend. So the user table for all these three applications are same. If a user is part of serviceapp using the same email and credentials he can login into subscriptionapp or ecomapp and vice versa.
The reason behind choosing same user table and other table for all the application is puerly business perspective - same single crm and ticketing system for sales and cdm team to track everything. Devise is being used in all three applications along with LDAP so login and signup works fine without any issue.
Problem:
Till now users' last_login_at is a single column so we really can't tell which app he last logged in at. But now we have to start logging these details separately, like when did he last login at serviceapp, ecomapp, subscription app separetly.
Also we are starting to use a new crm of one particular app - subscriptionapp and for the clients(users) of that particular app we have to store extra information like unq_id from crm and so on.
My intial thought is to add these columns in the user table itself. But in the future we might add few extra information to user table which are app specific. Hence adding it to the main user table won't be a good idea for this. How shall I proceed in this case? I though of creating three different tables like subscriptionapp_client, ecomapp_client, serviceapp_client had associating them with the user table like user has_one ***_client.
If the association is present like if user.subscriptionapp_client.present? he is a client of that app and we can store the last login at, crm_uniq_id and all in there in that table itself.
Is there anyother good approach that might fit the problem here? I am reading about MTI but it looks like it won't solve the problem.
Single table inheritance with JSON.
class CreateClientAccount < ActiveRecord::Migration[5.2]
def change
create_table :client_accounts do |t|
t.references :user
t.string :uid # the unique id on the client application
t.string :type
t.integer :sign_in_count
t.datetime :last_sign_in_at
t.jsonb :metadata
t.timestamps
end
add_index :client_accounts, [:user_id, :type], unique: true
end
end
class User
has_many :client_accounts
has_one :service_account, class_name: 'ServiceApp::ClientAccount'
# ...
end
class ClientAccount < ApplicationRecord
belongs_to :user
validates_uniqueness_of :user_id, scope: :type
end
module ServiceApp
class ClientAccount < ::ClientAccount
end
end
module SubscriptionApp
class ClientAccount < ::ClientAccount
end
end
module EcomApp
class ClientAccount < ::ClientAccount
end
end
This avoids the very unappealing duplication of having X number of tables in the schema to maintain and the JSONB column still gives you a ton of flexibility. However its in many ways just an upgrade over the EAV pattern.
It also has a lot in common with MTI. In MTI you would use an association to another table which fills the same purpose as the JSON column - to make the relational model more flexible. This can either be polymorphic or you can have X number of foreign keys for each specific type.
One table for each type.
class User < ApplicationRecord
has_one :subscription_account
has_one :service_account
# ...
end
class ClientAccount < ApplicationModel
self.abstract_class = true
belongs_to :user
end
class SubscriptionAccount < ClientAccount
end
class ServiceAccount < ClientAccount
end
# ...
This is the most flexible option but if you want to add a feature you will have to create migrations for each and every table. And this also means that you can't query a single homogenous collection if you want all the types. You have to perform X number of joins.
This is not really that appealing unless the requirements for each type are wildly different.
Related
I previously created a joined table with the migration:
class CreateJoinTableCategoryListing < ActiveRecord::Migration[5.2]
def change
create_join_table :categories, :listings do |t|
# t.index [:category_id, :listing_id]
# t.index [:listing_id, :category_id]
end
end
end
I was looking back on this as i am going to be creating a new join tables. But while looking at it i noticed i migrated with the t.index's still commented out and the joined tables still operate correctly.
I read into this and i haven't found any posts about either someone doing the same or not needing them.
How is it operating with those index's never migrated and how needed are they?
I am creating a new migration:
class CreateJoinTable < ActiveRecord::Migration[5.2]
def change
create_join_table :users, :affiliates do |t|
# t.index [:user_id, :affiliate_id]
# t.index [:affiliate_id, :user_id]
end
end
end
Which index should i be choosing here?
How it should work is that an affiliate is able to manually submit a "commission" to the table (which does need to be added to the migration), but if the commission is updated, it should take the place of the column and not create a new row.
A user will have really nothing to do with this and will be mostly updated by the affiliate to update the commission rates they have on the user.
Update:
Is it even possible to add another field to the join table?
I wanted to add a :commission to the table but i can't find any docs to do anything for that. Should i just be defining the commission rate within the users table and do away with the join table?
UPDATE 2:
Ended up scratching this idea and keeping my current method of doing it with the users and affiliates association only. I did away with the UsersAffiliates idea as it's not needed for this case.
How is it operating with those index's never migrated and how needed
are they?
All types of assocations in Rails will work without indices. The only thing that is required is that the correct tables and columns exist.
Indices are however critical for performance as the size of your database grows. They also provide constraints such as uniqueness that ensure that duplicate data cannot be inserted due to race conditions.
Which index should i be choosing here?
The whole reason that Rails generates two different indices is that you should choose the index that cooresponds to how you will most often be searching the table. If you are most often using User.joins(:affilitates) you would choose t.index [:user_id, :affiliate_id] for example.
How it should work is that an affiliate is able to manually submit a
"commission" to the table (which does need to be added to the
migration).
The create_join_table macro creates a join table named for has_and_belongs_to_many assocations.
The main problem with has_and_belongs_to_many assocations is that they are headless. There is no model and therefore no way to query the table directly or add additional metadata columns.
What you instead want is a has_many :through association.
class User < ApplicationRecord
has_many :user_affiliates
has_many :affiliates, through: :user_affiliates
end
class Affiliate < ApplicationRecord
has_many :user_affiliates
has_many :affiliates, through: :user_affiliates
end
# call this whatever you want
class UserAffiliate < ApplicationRecord
belongs_to :user
belongs_to :affilitate
end
While has_and_belongs_to_many uses the table naming scheme users_affilities (plural_plural) you want to use user_affilities for a has_many through: association.
You can fix this by:
Just generating the table/model through the normal generator rail g model user_affiliate.
If the table exists write a migration to rename the table.
but if the commission is updated, it should take the place
of the column and not create a new row.
You can solve this by:
Add a unique compound index on the two columns t.index [:user_id, :affiliate_id], unique: true.
Add a uniqueness validation in the join model. validates_uniqueness_of :user_id, scope: :affiliate_id.
Use .find_or_initialize_by in your controller to update an existing row if it exists instead of creating a new row if one already exists.
In designing a data model, you will sometimes find a model that should have a relation to itself. Ruby On Rails Guide provides a neat example. I am using it as a template for my example
For example, you may want to store all users in a single database model, but be able to trace relationships such as between affiliate and users. This situation can be modeled with self-joining associations:
class User < ApplicationRecord
has_many :users, :through => :referrals
has_one :affiliate, :through => :referral
end
This allows me to keep both users and affiliate in a same database table which is correct because fundamentally they are all individual users.
Problem arises when the model grows. Affiliate has its own set of methods - earnings, expected_earnings etc. These methods are very specific to Affiliate and I have my qualm keeping them with other user methods.
Loading object in correctly named variable helps:
affiliate = User.find 1
affiliate.earnings # used in context of affiliate
user = User.find 1
user.subscriptions # mostly in context to user
But when I read the User model, Affiliate related methods feels out-of-place.
Is there a way to namespace these methods correctly? What is the standard way of organizing self join model methods?
One way to solve this is with Single Table Inheritance. Before accepting this approach, I would recommend searching the web for "single table inheritance rails" and reading up on the pros and cons of it. A lot of digital ink has been spent on this subject.
With the caveat out of the way, Single Table Inheritance (STI) allows you to let multiple Rails models share one database table. You do this by adding a string field called type to your database table. Rails will interpret this as the subclass of your model. You would then create several models that inherit from User.
In your specific case, the type field would either contain user or affiliate. You would also create an Affliliate class which inherits from User. All of your Affiliate specific methods would be put in the Affiliate class. Rails is smart enough to use the type field in the database to identify records from the appropriate class.
Here is the migration you would run:
class AddTypeToUsers < ActiveRecord::Migration[5.1]
def change
add_column :users, :type, :string
add_index :users, :type
end
end
Next you would add an Affiliate class:
# app/models/affliliate.rb
class Affiliate < User
# Affiliate specific methods here.
end
You may also want to create a class for non-affiliate users. Call it customers:
# app/models/customer.rb
class Customer < User
# Customer specific methods here.
end
Use the appropriate class name when creating new records and rails will automatically populate the type field in the database.
You would then moving your associations to the appropriate model:
# app/models/affiliate.rb
class Affiliate < User
has_many :customers, through: :referrals, foreign_key: :user_id
end
# app/models/customer.rb
class Customer < User
has_one :affiliate, through: :referral, foreign_key: :user_id
end
I have not tested this, but it should work.
I need to know, if there is a way to deal with different type of users/clients in Rails using a single model.
What I really need to do?
- I need to save a different type of clients in my database. So, I have this migration:
class CreateClients < ActiveRecord::Migration
def change
create_table :clients do |t|
t.string :name # Person name if "Personal", Company name if type "Company"
t.string :nif # fillable only if type is Company
t.string :identity_card_number # fillable only if type is Personal
t.integer :client_type # Type can be Personal or Company
t.references :company, index: true # if type is personal, it can belong to a company
t.timestamps null: false
end
end
end
Then I create this model
class Client < ActiveRecord::Base
has_many :employees, class_name: 'Client', foreign_key: 'company_id'
belongs_to :company, class_name: 'Client'
end
Note: A personal account can belong to a company or not.
Based on your experience, am I doing this in the right way? There are another way to do that?
EDIT:
Hi #manfergo25,
Whit this I have another question. "Company" and "Personal" are both "Clients Account", in that way, both must be able to buy services.
If I need to associent the client with the service, can I do this?
class Personal < Account
has_many :services
end
and
class Service < ...
belongs_to :account
end
??
The right way is Single Table Inheritance (STI) as Sontya say.
class Account < ActiveRecord::Base
end
Then,
class Client < Account
end
class Provider < Account
end
You only have to add a type column in 'Account' to contain a string representing the type of the stored object.
For example in a controller you could do this:
account = Client.find(params[:autocomplete_client])
params[:service][:account_id] = account.id
#service = Service.new(params[:service])
You can use STI(Single Table Inheritance)
class Account < ActiveRecord::Base
end
class Company < Account
has_many :services, :dependent => :destroy
end
class Personal < Account
has_many :services, :dependent => :destroy
end
class Service < ActiveRecord::Base
belongs_to: personal
belongs_to: company
end
With the above definition, a personal and company should be able to buy services.
and you should be able to call
#company.services # it will return you the number of services of company
#personal.services # it will return you the number of services of personal
Personally, I think the way you have suggested in your question is the way I'd do it.
While Single Table Inheritance is intellectually better modelling -- I've found STI to be a bit hard to work with and sometimes unreliable in Rails. And probably won't improve the cleanliness or conciseness of your code all that much in the end anyway. STI is good to keep in mind as an option, if you find the OP approach is not working well as far as allowing you to write clear concise code and it seems STI could work out better.
But I'd start without STI, with just the one Client class, as you've outlined in your question. If you later add STI, you'd still have the Client class, you'd just have sub-classes for, say, PersonalClient and CompanyClient. It won't be that hard to switch to STI later if you want to, although it might require some minor db schema alterations.
But I don't think it'll get you enough benefit to justify the added complexity, in an area of Rails that has sometimes had some rough edges.
Here's some more info about STI and it's plusses and minuses: http://eewang.github.io/blog/2013/03/12/how-and-when-to-use-single-table-inheritance-in-rails/
is there any way when creating migrations to predefine the values an attribute can have?
class User < ActiveRecord::Base
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :users
end
and my migrations:
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.string :name # (regular, admin, etc) => how can we define these as a list?
t.timestamps
end
end
end
class AddRoleToUser < ActiveRecord::Migration
def change
add_column :users, :role_id, :integer
end
end
One approach is to use a validation like in this question and its two good answers. This validates that the value in a given column is one of a defined list of values. You could either add that validation to your User model and ditch the Role model entirely, or add it to the name field of Role. This is probably the simplest, most Rails-ish way to do it. You could define a constant on your application which contained the approved list of roles, then use whatever works best for you to maintain that list.
Another approach is to make your Role model an ActiveHash model rather than ActiveRecord. ActiveHash lets you set up models which act like ActiveRecord models, but rather than being stored in the database, they're read-only models defined by a hash in the model file. (I'm grossly oversimplifying how ActiveHash works, I expect, but it's a start.) Again, you could use an application constant to maintain the hash.
The advantage to retaining Role as a model is that it allows for Users to have multiple Roles if you ever decided to go that route.
I'm about to build my first Ruby on Rails application (and first anything beyond xhtml and css), and I'm looking for some helpful feedback on my model structure.
I've included a mockup to help visual the application.
The only models I'm sure I need so far are:
1. a movie model (to serve as the main model, named movie so URL's will look like, "sitename.com/movies/1-Willy-Wonka") and
2. a user model
Other than those, I don't know what to make the others; like 'filming locations', 'actors', and most importantly, the attributes like 'willy wonka' and 'johnny depp'.
Should I make all of those models, even the attributes? If so, is it possible, and recommended, to let people create models as attributes?
I try to see the app as data created by forms and stored in databases, and with that pov I can't decide how to connect attributes (character <=> actor) to sections (actors, filming locations, artwork, etc.) inside a movie listing; while allowing the attributes themselves to be hyperlinks to related attributes/movies/even sections: clicking on 'steven speilberg' would bring you to a page with movies he's acted in, films he's directed, produced, written, etc.
Thoughts?
I would recommend getting your data model clear first. You can then start with one rails model per database table. This is not always the case, but it's a reasonable place to start.
Let's focus on the movies and actors:
Some assumptions:
movies can have a many-to-many relationship to the other entities
you want to store the character information as part of the relationship between an actor and a movie
You might then model your relationships like this:
# movies.rb
class Movie < ActiveRecord::Base
has_many :roles
has_many :actors, :through => :roles
end
# actor.rb
class Actor < ActiveRecord::Base
has_many :roles
has_many :movies, :through => :roles
end
Normally, you can rely on Rails' magic to handle the join model without creating one yourself. In this case we want to store the character information as attributes of the join model, so we create it explicitly.
# role.rb
class Role < ActiveRecord::Base
belongs_to :movie
belongs_to :actor
end
Start with models Movie, Actor, FilmLocation, and Character. Movie and Actor have a many-to-many relationship(A Movie has many actors, an actor worked on many movies). FileLocation and Movie also are many-to-many. Character(Willy Wonka) and Actor are many-to-many as well.
If this is your first anything as far as web development then I suggest you start with writing a simple web app that just lists movies and allows your to add, edit and delete them. Just store the title, a synopsis and maybe the URL of the movie poster/dvd cover. Then work on adding Actors and associating them with the Movie.
Creating an "IMDB-like" site is not a trivial project. You're going to have a lot complex relationships beyond just associating an actor with a movie. Actors have Roles in Movies. And you might want to get even more abstract than that and say that a Person has Job in a Movie which would allow you to also track things like the Directors, Producers, Key Grips, Casting Directors.
Not only should you work on your data model, but work on a plan about what you want the site to consist of and what order you should create those features in (based on need) and take small steps to get to that final goal.
Further elaboration. The migrations for the models listed above might look like this:
class CreateMovies < ActiveRecord::Migration
def self.up
create_table 'movies' do |t|
t.string 'title', :null => false
t.timestamps
end
end
def self.down
drop_table 'movies'
end
end
class CreateActors < ActiveRecord::Migration
def self.up
create_table 'actors' do |t|
t.string 'first_name', 'last_name', :null => false
t.timestamps
end
end
def self.down
drop_table 'actors'
end
end
The movie_id and actor_id fields below correspond to the belongs_to associations in the Role model above and are the foreign keys that allow the roles table to join actors and movies. As I have proposed modeling it, the character_name is a property of this relationship, and is thus an attribute of the role. It's your call whether you allow NULL in the character_name. I'm leery of NULL, but in this case I've allowed it as one could make the argument that in practice you often wish to store the fact that an actor is in a given movie but don't know or care about the character name.
class CreateRoles < ActiveRecord::Migration
def self.up
create_table 'roles' do |t|
t.integer 'movie_id', 'actor_id', :null => false
t.string 'character_name'
t.timestamps
end
end
def self.down
drop_table 'roles'
end
end
It might be worthwhile to read up on database normalization: http://en.wikipedia.org/wiki/Database_normalization
In general, yes. I like my models to be as granular as possible. This makes things much clearer for someone who isn't familiar with the application and makes it easier to reuse code.
It's helpful to have a firm grasp of ActiveRecord associations before writing a complex application. Make sure you know all of the associations by heart and what they actually generate in terms of SQL tables. Sometimes it can seem like magic, and magic isn't necessarily a good thing. If you know what's behind it things fall in to place.
Also, don't be afraid to scrap everything and start over while experimenting. It's Ruby, so it won't take long to get back to where you were.