Class inheritance in Ruby on Rails - ruby-on-rails

I'm building this little app with ruby on rails.
In the app, I'm having the following models strutter which I'm not sure whether it's the best ror practice. (I'm actually a Java developer)
There is a person class which I use it as a super class, and there are staff class and customer classes inherits from the person class. To achieve this, I have:
class Person < ActiveRecord::Base
end
class Staff < ActiveRecord::Base
belongs_to :person, :polymorphic => true, :dependent => :destroy
end
class CreateStaffs < ActiveRecord::Migration
def self.up
create_table :staffs do |t|
t.references :person, :polymorphic => true, :null => false
....
Firstly, what I did works fine, but am i doing the best thing?
The next thing I'm trying to do is to create a form which creates person, staff objects and link them. And I'm stuck on having two models on a single form.
Does anyone have suggestions?
Thanks,
Kevin Ren

What you're doing here is not to use Person as a superclass of Staff, but you create a relation between them. You want to look at single-table inheritance instead. You essentially want this:
class Person < ActiveRecord::Base
end
Class Staff < Person
end
In addition you need to have a "type" field in your Person table that Rails uses to figure out which model a given record belongs to. See the docs for ActiveRecord for more info.

Related

Generic model that has many relationship depending on value of enum

I have the following class:
class Blog < ApplicationRecord
enum platform: {
wordpress: 'wordpress',
drupal: 'drupal',
}
has_many :wordpress_posts
has_many :drupal_posts
end
Besides platform, it also holds things like url and category. It also has a relationship of has many with WordPressPost and DrupalPost:
class WordPressPost < ApplicationRecord
belong_to :blog
end
class DrupalPost < ApplicationRecord
belongs_to :blog
end
I would like to know, if it's possible to infere what has_many relationship should be the valid, depending on the platform value: if the platform value is wordpress, the blog should only contain relationships with wordpress post entities. I'm not sure if there is a Rails way to solve it. I would love if someone can help me and show me the proper Rails way of implement such data model.
Thanks
Your idea of using a enum won't work here since assocations are class level and the value of the enum is only known on the instance level.
If you really wanted to use an enum you could hack something together with an instance method but it won't really behave like an assocation when it comes to stuff like eager loading:
class Blog < ApplicationRecord
# ...
def posts
send("#{platform}_posts")
end
end
What you can do is use Single Table Inheritance to setup classes that share a table yet have different behavior.
First add a type column to the table:
class AddDetailsToBlogs < ActiveRecord::Migration[6.0]
def change
change_table :blogs do |t|
t.remove :platform
t.string :type, index: true, null: false
end
end
end
If you have existing data you should go through it and set the type column based on the value of platform before you drop platform and make type non-nullable.
Then setup the subclasses:
class Blog < ApplicationRecord
# shared behavior
end
class WordPressBlog < Blog
has_many :posts,
class_name: 'WordPressPost',
foreign_key: :blog_id,
inverse_of: :blog
end
class DrupalBlog < Blog
has_many :posts,
class_name: 'DrupalPost',
foreign_key: :blog_id,
inverse_of: :blog
end
The main advantage of STI is that it lets you query as a single table and thus treat it as a homogenous collection, the drawbacks are that you are potentially wasting database space with columns containing largely nulls and it can become quite unweildy if the types differ to much from each other.

Passing a child to container of parent type

I have a parent class Individual and child classes Student and Professor in my rails application.
Inheritance is handled with a gem called 'acts_as_relation' which simulates multiple table inheritance.
In addition, I have an action within which a student instance is appended to a list of individuals. Normally I would have expected this to go through without any problems but I get this error:
ActiveRecord::AssociationTypeMismatch: Individual(#70220161296060) expected, got Student(#70220161349360)
Here is a glance at my model:
class Individual < ActiveRecord::Base
acts_as_superclass
end
class Student < ActiveRecord::Base
acts_as :individual
end
class Professor < ActiveRecord::Base
acts_as :individual
end
I've not used this gem, but to give you some help, here's what I've found and this:
They both mention that you're calling an object through your relation, which will have confusion over polymorphism or similar. The two posts could not fix the issue, and I presume that is because they could find the correct object for their relationship
Looking at this further, I found this tutorial on the gem homepage:
acts_as_relation uses a polymorphic has_one association to simulate
multiple-table inheritance. For the e-commerce example you would
declare the product as a supermodel and all types of it as acts_as
:product (if you prefer you can use their aliases is_a and
is_a_superclass)
class Product < ActiveRecord::Base
acts_as_superclass
end
class Pen < ActiveRecord::Base
acts_as :product
end
class Book < ActiveRecord::Base
acts_as :product
end
To make this work, you need to declare both a foreign key column and a
type column in the model that declares superclass. To do this you can
set :as_relation_superclass option to true on products create_table
(or pass it name of the association):
create_table :products, :as_relation_superclass => true do |t|
# ...
end
Or declare them as you do on a polymorphic belongs_to association, it
this case you must pass name to acts_as in :as option:
change_table :products do |t|
t.integer :producible_id
t.string :producible_type
end
class Pen < ActiveRecord::Base
acts_as :product, :as => :producible
end
class Book < ActiveRecord::Base
acts_as :product, :as => :producible
end
Are you sure you've got your datatables set up correctly?
The way I've solve this in my projects, is using instance_ofsome_class.individuals << student_instance.individual.
The thing here is that is not a real MTI, so your collection of individuals would accept only individuals instances. If you call some_student_instance.individual or some_professor_instance.individual, you'll get an individual instance which is related with your specific instance.
Then working with that collection, if you want a Student or Professor all you need to do is call individual_in_collection.specific. For example:
p = Professor.create
a_model.individuals << p.individual
puts "#{a_model.individuals.first.class.name}"
=> Individual
puts "#{a_model.individuals.first.specific.class.name}"
=> Professor

Is there a way to set up a :has_many association through a callback or other method?

More specifically, let's say I have a model
class User < ActiveRecord::Base
:has_many => (xxxImages)
end
Where xxx can be one of different models in my application. For example:
class ABCImages < ActiveRecord::Base
:belongs_to => User
end
class EFGImages < ActiveRecord::Base
:belongs_to => User
end
What I'm basically asking is: is there any way to pick one of those models at runtime to be inserted into the User models has_many association? Or do I need to take the polymorphic route (which I've only read about slightly so I'm not too familiar with it yet)
Thanks!
I think Single Table Inheritance is the way you should go which will harness the power of having different models but rather store in the same table. The only thing needed for this is to add a database field called :type.
So, in you case, I would create a table names base_images and other two classes would be just a subclass of this class.
So, the migration for this base class would look like this,
class CreateBaseImages < ActiveRecord::Migration
def change
create_table :base_images do |t|
t.string :type
t.string :url
t.references :user
t.timestamps
end
end
end
Now, after you migrate this, it will create a model base_image.rb, as below,
class BaseImage < ActiveRecord::Base
belongs_to :user
end
That's it. Now, that we have the BaseImage we will create two different models namely AbcImage and EfgImage which would inherit from BaseImage class.
class EfgImage < BaseImage
end
class AbcImage < BaseImage
end
And our user class would look like this,
class User < ActiveRecord::Base
has_many :base_images
has_many :abc_images
has_many :efg_images
end
With this code in place, you can create association called abc_images or egf_images to user through association which works like a single table. And if you were to call base_images, it would fetch all the images, irrespective of which subclasses they belong.
You will find this so much resuable that later if you intend to create hij_images association then creating class HijImage and inheriting it from the BaseImage class and it simply works. By, the way there is no magic here, rails stores the name of the class into the type field in database. And so when you query for a certain class, it creates the predicate with type and the name of the class.

Rails: polymorphic assoziation, has_many :through

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.

Designing a Rails application: single table inheritance?

I've been researching this for a few days now, and I know there is an abundance of articles that talk about single table inheritance and polymorphic associations with Rails. A lot of helpful material is from 2009 or before and I'm wondering if there is a better approach to this problem now.
The situation
The application has a couple of different user types (ie, Buyer and Seller), each of which have a profile. The profile for each user type is really different, so I'm currently ruling out the idea of one generic "profile" table.
My current approach
which is the same as this solution.
class User < ActiveRecord::Base
# authentication stuff
end
class UserType1 < User
has_one :user_type_1_profile
end
class UserType2 < User
has_one :user_type_2_profile
end
...
class UserTypeN < User
has_one :user_type_n_profile
end
Based on my research, this is kind of a "mixed model" design.
Honestly, at this point I don't know of any other solution that would work. Every time I've seen similar questions asked I see the idea of polymorphic associations brought up. Could someone elaborate how a polymorphic association would work in this case?
Does anyone have any other design suggestions?
You would be better off with a polymorphic association here with various profile types.
class User < ActiveRecord::Base
  belongs_to :profile, :polymorphic => true
end
class ProfileTypeA < ActiveRecord::Base
  has_one :user, :as => :profile
end
class ProfileTypeB < ActiveRecord::Base
  has_one :user, :as => :profile
end
Which would require you have a migration/table like this:
change_table :users do |t|
t.integer :profile_id
t.string :profile_type
# OR (same thing, the above is more explicit)
t.references :profile, :polymorphic => true
end
There's more info on this in the Guide: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

Resources