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
CodePudding user response:
models/person.rb
class Person < ApplicationRecord
has_many :tasks, as: :owner, class_name: "::Task"
end
models/task.rb
class Task < ApplicationRecord
belongs_to :owner, polymorphic: true
enum status: {
active: 0,
done: 1
}
scope :chores, -> { where(type: "Tasks::Chores")}
scope :homeworks, -> { where(type: "Tasks::Homework")}
end
models/tasks/chores.rb
class Tasks::Chores < Task
enum size: {
small: 0,
medium: 1,
large: 2
}
end
models/tasks/homework.rb
class Tasks::Homework
end
Welcome to Stackoverflow !
I haven't tried this code but it should be correct.
Task
has a polymorphic owner
, so it can be a Person
, or any other model, this will add 2 columns owner_type
and owner_id
.
A task also has a type
(STI) which can be either Tasks::Chores
or Tasks::Homework
(which I namespaced for clarity)
Using the scopes on the Task
model you should be able to call person.tasks.chores
or person.tasks.homeworks
CodePudding user response:
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.