I'm trying to learn about concerns.
Most of the blog posts and examples I can find discuss them in the context of moving class methods defined in a model to a common repository. I understand that part.
I don't understand whether concerns can be used to reduce the setup for associated models. For example, I have a user model and an organisation model. Each of user and organisation will have an address.
If address is a model, it will be polymorphic and belong to addressable. Then user and organisation will each have one address.
I am trying to understand whether I can make address a concern and then include it in my user and organisation models. If so, can I still have a table in the database called address? It's not clear to me whether I can have the db table if I don't have a model called address (which I wouldn't need if I used concerns subfolder in the models folder to define address).
Yes, you can certainly do this, and it's pretty common. You'll still need an Address model and addresses table in the database.
It would look something like this:
# your user model (backed by users table)
class User < ApplicationRecord
include Addressable
end
# your organisation model (backed by organisations table)
class Organisation < ApplicationRecord
include Addressable
end
# your address model (backed by addresses table)
class Address < ApplicationRecord
belongs_to :addressable, polymorphic: true, touch: true
end
# the concern to DRY up shared relation that both user adn organisation have
module Addressable
extend ActiveSupport::Concern
included do
has_one :address, as: :addressable, dependent: :destroy
end
end
Related
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'm the middle of creating a RoR application which needs a Many-to-many association between the same table (at least in theory).
How so? Well, I'd need a User table which contains two kind of users: Server, and client, more or less like the idea of a Teacher and a student (with private lessons, but with multiple teachers), or a Doctor and a Patient
My first idea was to simply make a User table (you know, login, email, and personal info) and assign it a Role (Server, or client), but then I thought that making such association with a third-table would troublesome
USER <-----> USER_USER
But the idea of creating two "login" tables that represent each role, and a third-table for the association sounds wrong.
Client_Login <-----thru---> Client_Server <---thru---> Server
For simplicity sake, a client cannot be a server to another clients, and a server cannot be a client for another server.
Obviously, a server can have multiple clients, and a client has multiple servers
How would recommend modeling this relationship?
If you need to explicitly have different methods between the two, Server and Client, which I am assuming since you want different classes. Then you might want to look into Single Table Inheritance(STI). This will allow you to use one User table, but have two different models that use it.
class User < ActiveRecord::Base
belongs_to :another_model #example association that will exist for all user types
self.inheritance_column = :role
# if you need to be able to tell what role are available
def self.roles
%w(Client Server)
end
end
class Client < User
has_many :server_clients
has_many :servers, through: :server_clients
end
class Server < User
has_many :server_clients
has_many :clients, through: :server_clients
end
You then have to just setup a simple server_client.rb model for the bridge.
example from here: http://samurails.com/tutorial/single-table-inheritance-with-rails-4-part-1/
This will allow you to put common functionality for all Users in the User class, and specific functionality in the respective classes of Server and Client.
It's done all the time. It's quite common to have a many-to-many back to yourself. It's common in hierarchies dealing with people's relations to each other, (dependency, managers, children, etc... )
class User
has_many :user_relations, dependent: destroy, inverse_of: :user
has_many :dependent_users, through: :user_relations
has_many :dependent_upon_users, through: user_relations, source:
:dependent_upon
end
class UserRelation < ActiveRecord::Base
belongs_to :user
belongs_to :dependent_upon, class_name: User
validates_presence_of :user, :dependent_upon
end
After spending a few days of trying to find an answer online, I figured I should ask for help. I'm trying to figure out the best way to implement these relationships using Rails Associations.
I have 4 models: User, Transfer, Building and Bag.
A User has a role attribute. The possible values for role are 'admin', 'building_contact' and 'guest'.
This is what it looks like in a tree structure. The Admin, Guest and BuildingContact is the role of a #user:
Admin
|
Transfer
/ \
Guest Building
| |
Bag BuildingContact
Therefore:
Admin has_many :transfers
Transfer belongs_to :admin
Transfer has_many :guests
Guest belongs_to :transfer
Guest has_many :bags
Transfer belongs_to :building
Building has_many :transfers
Building has_many :building_contacts
What is the best way to implement the relationship with the User model?
Thanks in advance!
One of the fundamental questions that I'd as is:
Will a user have more than one role? I'm guessing not, since you have a single role attribute. They may share many things, but I'd say that the business logic for the different user types would create different models. How about ditching that role attribute and instead using a type attribute and using STI?
class Admin < User; end
class Guest < User; end
class BuildingContact < User; end
That way you will have the inherited User capabilities but you can define differing business logic where appropriate. You can extend this into different controllers and views, otherwise I would think you would risk having a much bigger User class than you would want to.
I have a base table called users which holds all common information about a user such as name, address, phone number...etc
I have another table called clients which holds specific information about a client (such as the client's company name and their url) and inherits user information from the users table. A client has a foreign key user_id which maps back to the information about a user.
I have another table called client_admins which hold specific information about client_admins and also has a user_id field AND a client_id field (which links to the clients table).
I have another table called super_admins which links to the users table and has specific information about a Super admin.
I know I could probably get away with Single Table Inheritance as there is not a lot of different data between each of the types, just different functionality and privileges.
What is the best way to model this in Rails 3?
Inside your user model:
has_one :client
has_one :client_admin
has_one :super_admin
Inside your client model:
belongs_to :user
has_one :client_admin
Inside your client_admin model:
belongs_to :user
belongs_to :client
Inside your super_admin model:
belongs_to :user
I am unsure of whether this is exactly what you were aiming for, but with my citier gem you could do this:
class User < ActiveRecord::Base
acts_as_citier
# User methods and validation, inherited by all children (so client & super admins)
# Useful for things like validating user permissions etc
end
class Admin < User
acts_as_citier
# Admin specific methods and validation, inherited by all children
end
class Client < ActiveRecord::Base
end
class ClientAdmin < Admin
acts_as_citier
belongs_to :client
# Client admin specific methods and validation
end
class SuperAdmin < Admin
acts_as_citier
# Super admin specific methods and validation
end
Allows each model to have unique fields so better than STI, but also shares methods and properties like normal inheritance.
Check it out - http://peterhamilton.github.com/citier/
I am building an inventory management application with four different user types: admin, employee, manufacturer, transporter. I haven't started coding yet, but this is what I'm thinking.. Manufacturers and transporters are related with has_many :through many-to-many association with products as follows:
class Manufacturer < ActiveRecord::Base
has_many :products
has_many :transporters, :through => :products
end
class Product < ActiveRecord::Base
belongs_to :manufacturer
belongs_to :transporter
end
class Transporter < ActiveRecord::Base
has_many :products
has_many :manufacturers, :through => :products
end
All four user types will be able to login, but they will have different permissions and views, etc. I don't think I can put them in the same table (Users), however, because they will have different requirements, ie: vendors and manufacturers must have a billing address and contact info (through validations), but admins and employees should not have these fields.
If possible, I would like to have a single login screen as opposed to 4 different screens.
I'm not asking for the exact code to build this, but I'm having trouble determining the best way to make it happen. Any ideas would be greatly appreciated - thanks!
Your basic approach seems reasonable. I would advise you to make a base class of User and use STI for specific User types, for instance:
class User < ActiveRecord::Base
end
class Manufacturer < User
has_many :products
has_many :transporters, :through => :products
end
...etc. This way if there's ever the need to aggregate multiple user types into one relationship regardless of type, you have one table to describe Users in general. This is a fairly common approach.
Depending on how much access different users will have to the system, you may want to look at a Role Management gem like Declarative Authorization.
For Multiple user systems, generally preferred ways are - use of role model or STI. If your users can have multiple roles at same time, like single user being Manufacturer and transporter, then Role base system would be good solution. If users role is fixed, then i think you should go with STI.
I suggest you make a User model, Address model, ContactInfo model, etc. You should NOT have those kinds of fields in the User model. Normalize the database. Have a FK in each of those other classes to User.id.
If you MUST keep them separate, then normalize logins and make it polymorphic to reference its owner (manufacturer, employee, etc)