As a part of a project we got an UML diagram to implement. Now I'm at the part of OOP, but I'm not sure how to implement that in Ruby. Every Task is a Chore or Homework, as I understand it must be multi table inheritance(MTI). I'm not sure how to implement the relations between users table -> chores/hw's tables , also tasks->chores/hw's tables. Also, it will be good to know how to implement the CRUD actions (create\update\destroy) Hope somebody can help. Thanks. Here is the diagram: Diagram
Extend model in Ruby on Rails for RESTful api app
65 views Asked by Efim Risberg AtThere are 2 answers
On
Even though your UML schema violates Dependency Inversion principle when implement it in Rails and violates 2NF
But you can do it like this:
You need to create two tables people and tasks
You can do this by adding these migrations:
class CreatePeople < ActiveRecord::Migration[7.0]
def change
create_table :people do |t|
t.string :name
t.string :email
t.string :fav_prog_lang
t.timestamps
end
end
end
class CreateTasks < ActiveRecord::Migration[7.0]
def change
create_table :tasks do |t|
t.references :owner, null: false, foreign_key: { to_table: :people }
t.text :description
t.integer :size, default: 0
t.string :course
t.datetime :due_date
t.string :details
t.integer :status, default: 0
t.timestamps
end
end
end
After that you need to create these models:
# app/models/person.rb
class Person < ApplicationRecord
has_many :tasks, foreign_key: :owner_id
end
# app/models/task.rb
class Task < ApplicationRecord
belongs_to :owner, class_name: 'Person', foreign_key: :owner_id
enum status: { active: 0, done: 1 }
end
# app/models/chore.rb
class Chore < Task
enum size: { small: 0, medium: 1, large: 2 }
end
# app/models/homework.rb
class Homework < Task
enum size: { small: 0, medium: 1, large: 2 }, _prefix: true
end
And finnaly you are able to create you tasks:
person = Person.create(name: 'John Doe', email: '[email protected]', fav_prog_lang: 'Ruby')
Homework.create(owner: person, course: 'Some course', due_date: 10.days.since, details: 'Some details')
Chore.create(owner: person, size: :medium, description: 'Some description')
person.tasks
If you need to be able to know what task is chore and what task is homework you need to add type field to the tasks table so that to be able to determine subtask
Update:
To avoid violating Dependency Inversion principle you can do next:
Separate Task, Chore and Homework to different tables since Chore and Homework have very different fields
class CreateTasks < ActiveRecord::Migration[7.0]
def change
create_table :tasks do |t|
t.references :owner, null: false, foreign_key: { to_table: :people }
t.integer :status, default: 0
t.timestamps
end
end
end
class CreateChores < ActiveRecord::Migration[7.0]
def change
create_table :chores do |t|
t.references :task, null: false, foreign_key: true
t.text :description
t.integer :size, default: 0
t.timestamps
end
end
end
class CreateHomeworks < ActiveRecord::Migration[7.0]
def change
create_table :homeworks do |t|
t.references :task, null: false, foreign_key: true
t.string :course
t.datetime :due_date
t.string :details
t.timestamps
end
end
end
And than your models:
class Person < ApplicationRecord
has_many :tasks, foreign_key: :owner_id
has_many :chores, through: :tasks
has_many :homeworks, through: :tasks
end
class Task < ApplicationRecord
belongs_to :owner, class_name: 'Person', foreign_key: :owner_id
has_many :chores
has_many :homeworks
enum status: { active: 0, done: 1 }
accepts_nested_attributes_for :chores, :homeworks
end
class Chore < ApplicationRecord
belongs_to :task
enum size: { small: 0, medium: 1, large: 2 }
end
class Homework < ApplicationRecord
belongs_to :task
end
And you can manipulate those models like this:
person = Person.create(name: 'John Doe', email: '[email protected]', fav_prog_lang: 'Ruby')
person.tasks.create(
status: :done, chores_attrubutes: [{ size: :medium, description: 'Some description' }]
)
person.tasks.create(homeworks_attrubutes: [{ course: 'Some course', due_date: 10.days.since, details: 'Some details' }])
person.chores
person.homeworks
# and you can have also all tasks
person.tasks
Note: But this approach is not suitable if you will need to add more different tasks. Because all the time you will have a new task type you will have to add new table. This only works for your current example.
I haven't tried this code but it should be correct.
Taskhas a polymorphicowner, so it can be aPerson, or any other model, this will add 2 columnsowner_typeandowner_id.A task also has a
type(STI) which can be eitherTasks::ChoresorTasks::Homework(which I namespaced for clarity)Using the scopes on the
Taskmodel you should be able to callperson.tasks.choresorperson.tasks.homeworks