Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 5 years ago.
Improve this question
I want to create a base model Person with some person related attributes like name, address, phone and so on. One Person can be one ore more of the following:
LoginUser with fields for login, password, last_login, ...
CardHolder with fields for card_id, last_entrance, ...
Supplier with just a flag whether or not the person is a supplier
Recipient with just a flag whether or not the person is a recipient
Is there a common sense or best practise design pattern in Ruby on Rails to represent that inheritance? How it should be represented in the model(s) and table structure so that it is possible to check whether a Person is a LoginUser and to access the corresponding fields.
In another project I worked already with STI but in this case this isn't the right pattern.
What you're looking for is a reverse polymorphic association. Polymorphic associations allow you to link one model to many different ones. A reverse polymorphic association allows you to link many models to one single one. They're a little tricky to set up, but once you get the hang of it it's no problem.
In order to accomplish this, you need another model that acts as a go-between for the Person model and each of the different roles. This go-between model is the one that actually has the polymorphic association. Your Person model will has_many that model, and your various role models will has_one of it. You then use :through to make the rest of the necessary associations so your code doesn't know any different. Shazam!
Here's an example of how to do it with the Person and CardHolder models. I'm calling the extra model Role because that seems like an obvious choice:
class Person < ApplicationRecord
has_many :roles
# Reach through the Roles association to get the CardHolders, via polymorphic :rollable.
# Unfortunately, you can't has_one, so you'll have to enforce uniqueness in Role
# with a validation.
has_many :card_holders, through: :roles, source: :rollable, source_type: 'CardHolder'
end
class Role < ApplicationRecord
belongs_to :person
# Here is where our actual polymorphic connection is:
belongs_to :rollable, polymorphic: true
end
class CardHolder < ApplicationRecord
# The other side of the polymorphic connection, with has_one:
has_one :role, as: :rollable
# Get the person via the role, just like the inverse:
has_one :person, through: :role
end
The database setup is like this:
class CreatePeople < ActiveRecord::Migration[5.1]
def change
create_table :people do |t|
t.string :name
# put in whatever other Person columns you need
t.timestamps
end
end
end
class CreateRoles < ActiveRecord::Migration[5.1]
def change
create_table :roles do |t|
t.references :person, index: true
t.references :rollable, polymorphic: true, index: true
t.timestamps
end
end
end
class CreateCardHolders < ActiveRecord::Migration[5.1]
def change
create_table :card_holders do |t|
t.integer :card_id
t.datetime :last_entrance
# put in whatever other columns you need
t.timestamps
end
end
end
Using it is quite simple:
> p = Person.create(name: "Sven Reuter")
# directly add a card holder
> p.card_holders << CardHolder.create(card_id: 1, last_entrance: Time.current)
# build a role instead
> p.roles.build(rollable: CardHolder.new(card_id: 2, last_entrance: Time.current)
# get all of the roles
> p.roles
I would go with Person table and the PersonAttributes table that is a union of all the attributes the person might have. PersonAttributes might use STI if applicable, e.g. with LoginUser storing logins and CardHolder referencing Cards.
Clean and simple.
Related
I'm working with two different models Person and Organization. Among many other attributes, both a Person and Organization can be a Contractor. If I was working with just the Person model and wanted to store contractor information I would say that Contractor belongs_to :person and be done with it. However this seems like Contractor belongs to two other models.
I searched around on google and found a lot of info about how to assign ownership to two different models at once. (For example a Sale must belong to both a Buyer and a Seller.) But in my situation a Contractor is EITHER a Person or an Organization. Is there any way to elegantly store Contractor information for both models in the same table?
If not, I figure I can always make two different contractor tables, but I figured this might be an opportunity to learn something. Many thanks in advance.
Maybe you can try this. Rails provide polymorphic-associtions. You can try to build one model named ContractorInfo belongs to Contractable(use polymorphic: true), then Person has_one ContractorInfo as contractable, Organization has_one ContractorInfo as Contractable.
I agree with ShallmentMo, but as additional, You could define something like this:
Models
class Contractor < ActiveRecord::Base
belongs_to :contractable, polymorphic: true
...
end
class Person < ActiveRecord::Base
...
has_many :contractors, as: :contractable
...
end
class Organization < ActiveRecord::Base
...
has_many :contractors, as: :contractable
...
end
Migrations
create_table :contractors , force: true do |t|
t.references :contractable, polymorphic: true, index: true
...
t.timestamps null: false
end
Usage
def create
#contractor = Contractor.new(params[:contractor])
contractable = params[:contractor][:contractable].split(":")
#contractor.contractable_type = contractable[0] # String: 'Person' or 'Organization'
#contractor.contractable_id = contractable[1].to_i # Integer: id of 'Person' or 'Organization'
...
This question already has answers here:
Ordering First Model in Association with Second Model
(4 answers)
Closed 8 years ago.
What is the correct way to do many to many in Rails?
I wasted a bit of time on the subtleties of this, so I thought I would post the question and the answer here, in the case that it saves someone else some time.
I posted this question here, as it was not immediately apparent. So I wanted to make sure if others were in the same pickle that this hopefully helps them.
So firstly with Rails for those new to Rails.. when you generate a Model eg "Story", the generator will pluralise the Model when creating the table name for the database eg "stories". So eg when I ran "rails g model author_book", the model ends up called AuthorBook and rails names the table author_books.
Keep that in mind when choosing Model names, as initially "Story" was named "News", which violated the rails convention of having Models named in the singular form.
Also another note: Don't have really long model and primary key names, as when rails hooks up the many to many relation in the database it can cause issues as it is limited to a max of 64 chars (from what I can remember).
So for the example let say we have Books and Authors and a relation between them. Here is how they will be represented in rails:
app/models/book.rb
class Book < ActiveRecord::Base
attr_accessible :title
has_many :author_books #This is the database table name!!
has_many :authors, through: #This is the database table name!!
end
app/models/author.rb
class Author < ActiveRecord::Base
attr_accessible :name
has_many :author_books #This is the database table name!!
has_many :books, through: :author_books #This is the database table name!!
end
app/models/author_book.rb
class AuthorBook < ActiveRecord::Base
attr_accessible :author_id, :book_id
belongs_to :author #This is the MODEL name
belongs_to :book #This is the MODEL name
end
20131025011112_create_author_books.rb migration example:
class CreateAuthorBooks < ActiveRecord::Migration
def change
create_table :author_books, :id => false do |t|
t.belongs_to :author #this is the MODEL name
t.belongs_to :book #this is the MODEL name
end
add_index :author_books, [:author_id, :book_id], :unique => true #This is the database table name!!
end
end
I hope that saves someone else some time!
I'm wondering if you can help me determine the best associations and maybe new models to use for a feature I'm trying to add to an existing app. The app lets users ask questions that are answered by other users. When posting a question, a user indicates which Province it belongs to (because the questions are location specific) and the user also selects one or more categories for the question. Thus, after the questions created, I'm able to get category (choosing one category) and province information like this
def show
#question = Question.find(params[:id])
puts #question.categories.first.id #id = 2 for Tourism, for example
puts #question.province_id #id = 6 for Ontario, for example
end
What I'd like to do is let some users (who answer questions) become featured users whose profiles will be displayed down the side of the views/question/show page. So in the question show action, according to the above example, I'd to query for the featured users for Tourism category in the province of Ontario.
Considering that there will hopefully be thousands of users of the app, but only a small pool of featured users, I don't think it's best to store that information on the user model, but rather maybe create a featured_user model that connects to their main user profile, a province (for which they are an expert) and the category (for which they are an expert), but they'd only be pulled up when they match both 'province and category' at the same time, so a featured_user whose category is "Tourism" and province is "Ontario" will only be pulled up if the question is in both category "Tourism" and province "Ontario."
If I create a featured_user model, what columns and associations should I create with these other models to make it work efficiently, and/or what should I add to existing models? I'm not sure if I should be trying to use some has_and_belongs_to_many or has_many :through or simple foreign keys. My instinct is to make it more complicated than it probably should be.
Right now
Province.rb
has_many :questions
Question.rb
belongs_to :province
has_many :categorizations
has_many :categories, through: :categorizations
Category.rb
has_many :categorizations
has_many :questions, through: :categorizations
Categorization
belongs_to :question, touch: true
belongs_to :category, touch: true
User.rb
has_many :questions #i.e. questions they've asked
has_many :answers #i.e. questions they've answered
I think the featured user model is the best solution. This encapsulate all featured user logic in one place and you can implement it without adding attribute columns to other models that (as you say) only would be used for a very small number of users. Here is how i would implement this:
# CreateFeaturedUsers (migration)
create_table :featured_users do |t|
t.references :user
t.references :province
t.references :category
end
# FeaturedUser (model)
belongs_to :user
belongs_to :province
belongs_to :category
# User (model)
has_many :featured_users
# Question (model)
def featured_users
User.joins(:featured_users).where(featured_users: {province: this.province, category: this.category})
end
However i would properly name it something else then FeaturedUser as the model doesn't represent a user but only the relation to a user. Maybe Featuring would be a better name.
Im not sure i understand rails polymorphic.
In Java you can create Objects from the same Objecttype:
http://www.fh-kl.de/~guenter.biehl/lehrgebiete/java2/j2-08-Dateien/abb.8.10.jpg
Person trainer = new Trainer()
Person sportler = new Trainer()
In Rails http://guides.rubyonrails.org/association_basics.html#polymorphic-associations:
In this example: picture can be from an employee or from a product, sounds strange because this is not realy the same type.
Do i understand the real purpose: to save objects in the same container an array of person or image?
In my rails project: I have several person: sportsmen, trainer and guest. They are sons of person (inheritance).
I think i meet the inheritance reason.
There is another class named exercise.
Sportsmen and trainer can create exercises.
So i want to use polymorphic. Exercises can be from trainer or sportsmen. Like in the example of the rails page, images can be from employee or a product.
Do i meet the best practise?
How do i implement a has_many :through with polymorphy?
It is not possible to use a habtm assoziation with polymorphic.
You have to define a additional class, but how exactly?
I think you want single table inheritance (STI) models, not a polymorphic relationship.
See this article http://www.alexreisner.com/code/single-table-inheritance-in-rails and these stackoverflow answers Rails - Single Table Inheritance or not for Applicant/Employee relationship Alternative to Rails Single Table Inheritance (STI)?
Just to make it clear, you should use polymorphic associations when you have a model that may belong to many different models on a single association.
Suppose, you want to be able to write comments for users and stories. You want both models to be commendable. Here's how this could be declared:
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :comment, as: :commentable
end
class Product < ApplicationRecord
has_many :comment, as: :commentable
end
To declare the polymorphic interface (commendable) you need to declare both a foreign key column and a type column in the model.
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.text :body
t.integer :commentable_id
t.string :commentable_type
t.timestamps
end
add_index :comments, :commentable_id
end
end
You can check more details about associations here.
I'm modelling a scenario with Users and Tools, where a Tool is owned by one User but can be used by many Users including to one owning it.
I was thinking about adding an owner_id column to Tools and say it has_many Users or by adding a new relationsship table.
I'm really new to Rails and I have problems setting up the right associations in the models though, maybe you can point me in the right direction?
Thank you very much.
Your should add owner_id to the Tools table.
Associations will be like that.
class User < ActiveRecord::Base
has_and_belongs_to_many :tools
end
class Tool < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :owner, :class_name => 'User'
end
You'll need a tools_users table in order to use habtm-association. Generate a migration and create a table with option id: false and two columns user_id and tool_id:
class CreateToolsUsersTable < ActiveRecordMigration
def change
create_table :tools_users, id: false do |t|
t.integer :tool_id
t.integer :user_id
end
end
end
After that you can call something like #user.tools or #user.owner
Read more there
User has many tools
Tool belongs to user in owner
Tool has many users
is what I would do.
I'm not sure about the wording because I don't use Active Record but this is how it works in other orms