Home > Software engineering >  Extend model in Ruby on Rails for RESTful api app
Extend model in Ruby on Rails for RESTful api app

Time:06-18

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.

  • Related