Skip to content

Instantly share code, notes, and snippets.

@nathaniel-miller
Last active June 21, 2018 03:31
Show Gist options
  • Save nathaniel-miller/8662d2308f0967364b40f3bc3bacec03 to your computer and use it in GitHub Desktop.
Save nathaniel-miller/8662d2308f0967364b40f3bc3bacec03 to your computer and use it in GitHub Desktop.
Entity generators, migrations, and associations for initial caren webapp

Caren Entities

User

This is the entity that represents the individual who has the ability to 'login' using a device.

Generator

The scaffolding for this entity is currently being generated by the Devise gem.

rails g devise:install

However, we do need some extra fields for our user, namely, first_name and last_name. For this we need to modify the devise_create_users migration. Add:

#Custom
t.string :first_name
t.string :last_name

Associations

has_many :circles, foreign_key: "super_admin"
has_many :positions

The has_many :circles association gives the User the following functionality.

# returns an array of Circle objects.
user = User.first
user.circles

This functionality is used to identify which circles that user has created. This, however, is dependent on the Circle being set up correctly.

The has_many :positions associates provides the following functionality:

# returns an array of Position objects
user = User.first
user.positions

Circle

This is the entity that represents the 'project'. It is the glue that connects the users, tasks, posts, and everything else associated with taking care a single individual. This cannot exist without a User having created it first. Other users can then be associated with it.

Generator

The User who creates the Circle is known as the Super Admin. We want a Circle object to have the following functionality:

# return a single user
circle = Circle.first
circle.super_admin

We need to do some manual tweaking to achieve this. First, run the generator:

rails g scaffold circle super_admin_id:integer

In the generated migration add the following line after the change block:

add_foreign_key :circles, :users, column: :super_admin_id

The super_admin column is now a foreign key that points to the users table.

Associations

belongs_to :super_admin, class_name: 'User'
has_many :positions
has_many :task_generators
has_many :important_info_pieces
has_many :posts
has_many :tasks

The Circle entity has a belongs_to :super_admin association. However, it needs to point to the User model, hence the following line.

belongs_to :super_admin, class_name: 'User'

As it is a foreign key and a super_admin is required, a Circle must be created with an associated User. It must be created in the following manner or it will fail validation:

#create a new circle using the logged in user as super admin.
user = current_user
Circle.create(super_admin: user)

We can now retrieve the super admin of a specific Circle:

# return single super_admin
Circle.first.super_admin

It also has a has_many: positions association. This gives us the ability to list all associated Users and Roles. That would look something like the following:

# return an array of Position objects
Circle.first.positions

# return the role and name of users associated with a circle.
Circle.first.positions.last.role.name
Circle.first.positions.last.user.name

# "Captain"
# "Joey JoJo Jr. Shabadoo"

Role

This is the entity that represents a certain level of authorization. These are really just titles and nothing more. They are useful because they can be associated with a position (and by extension a user) and with authorization parameters. Users will not be able to create or modify these in anyway. They are created by the development team. Authorization is currently being handled through the use of the Pundit gem.

Generator

rails g scaffold role name:string

Associations

Roles are not dependent on anything. They are associated with positions but there is no real need to see every position associated with a role (role.positions) so there is currently no association given.

Category

This entity is another 'title-only' item. What gives it its usefulness is the association it can have with Task objects and ImportantInfoPiece objects.

rails g scaffold category name:string

Position

This is the entity that represents a specific user and the role he/she plays when it comes to a Circle. This is not to be confused with a Role. A Position is a single, unique item that is associates a User with a Role under a specific Circle.

Generator

rails g scaffold position role:references circle:references user:references

Associations

belongs_to :role
belongs_to :user, optional: true
belongs_to :circle

  has_one :invitation, dependent: :destroy

Some of the Position associations are a bit counter-intuitive. Specifically the belongs_to :role association. It's tempting to think that a role belongs_to a position or that a position has_one role. However, because we want the position to have a role method (position.role) the role_id attribute must be given to the position. This attribute is what dictates the the belongs_to: role association.

Create a position like so:

role = Role.first
circle = Circle.first
user = User.first # optional

Position.create(user: user, role:role, circle: circle)

# returns an array of positions
Circle.first.positions

Also, take note of the optional: true argument for the user association. It is likely that role will be created for a user who has not yet created an account. This optional parameter allows a position object to be created without having to specify the user immediately.

The Position also belongs_to :circle. This is here for the sake of an Invitation which references a Position. When a user gets an invitation to fulfill a specific position, we have access to the Circle that position belongs to.

As well, we can get information about a positions invitation (like whether or not it has been seen, rejected, etc) through the has_one :invitation association. The dependent: :destroy option deletes any existing invitation associated with that position.

# returns an Invitation object
position = Position.first
positon.invitation

Invitation

This entity represents the association of two Users and a Circle. It does this through a specific position (which belongs to a Circle.) When created, a recieving User will have the abiliy to accept the invitation. In doing so, his/her user account will then be associated with a specific Position object.

Generator

Creating the proper migration for this entity is a little more tricky than most as we need attributes labeled sender_id and recipient_id that are both foreign keys for a User. Creating a normal migration for this won't work as Rails expects the column name to simply be user_id.

Additionally, we have an email field that should never be empty This email address field is used to identify if any newly signed up user has any existing invitations. Because we are using Postgres we can get away with adding a database constraint (null: false)that prevents this field from being empty when a new invitation is created.

Rather than using the references datatype, create the column name and type manually.

rails g scaffold invitation accepted:boolean rejected:boolean position:references sender_id:integer recipient_id:integer email:string

The created migration file will need to be modified. Open it and add the following lines.

# The default values
t.boolean :accepted, default: false
t.boolean :rejected, default: false

# The email constraint
t.string :email, null: false

And added to the bottom of the change method, after the create_table block:

add_foreign_key :invitations, :users, column: :sender_id
add_foreign_key :invitations, :users, column: :recipient_id

Those lines inform Rails that the invitations table is to use the sender_id and recipient_id columns as foreign keys to the users table.

The migration is now possible but if we try and use it in this state, Rails will end up looking for Sender and Recipient classes. To prevent this we need the following:

Associations

belongs_to :position
belongs_to :sender, class_name: 'User'
belongs_to :recipient, class_name: 'User', optional: true

An Invitation is created like this:

# recipient DOES NOT yet exist
position = Position.first
user = User.first

Invitation.create(
  position: position,
  sender: user, 
  email: "caren@carecrew.ca"
)

# recipient DOES exist
recipient = User.last

Invitation.create(
  position: position,
  sender: user,
  email: recipient.email,
  recipient: recipient
)

Task Generator

This entity is responsible for creating new Task objects based on the values provided by the User. It is different than a task in that it should be thought of as a 'task factory' that creates task objects for reoccuring tasks.

Generator

rails g scaffold task_generator description:text category:references circle:references created_by_id:integer mandatory:boolean every_n:integer sun:boolean mon:boolean tues:boolean wed:boolean thurs:boolean fri:boolean sat:boolean part_of_day:integer custom_time:time last_run:timestamp look_ahead:integer

Modify the migration file as before to turn the created_by column into a foreign key:

add_foreign_key :task_generators, :users, column: :created_by_id

There are also a number of default values and a required description field as seen below.

t.text :description, null: false
t.references :category, foreign_key: true
t.references :circle, foreign_key: true
t.integer :created_by_id
t.boolean :mandatory, default: false
t.integer :every_n, default: 1
t.boolean :sun, default: false 
t.boolean :mon, default: false
t.boolean :tues, default: false
t.boolean :wed, default: false
t.boolean :thurs, default: false
t.boolean :fri, default: false
t.boolean :sat, default: false
t.integer :part_of_day, default: 1
t.time :custom_time
t.timestamp :last_run
t.integer :look_ahead, default: 7

t.timestamps

Associations

The assoications are as seen below.

belongs_to :category
belongs_to :circle
belongs_to :created_by, class_name: 'User'

We now have the following functionality:

# Create a new TaskGenerator object.
TaskGenerator.create(
  description: 'Rake leaves',
  category: Cateory.first,
  circle: current_circle,
  created_by: current_user
)

# Retrieve all TaskGenerators for a given Circle
Circle.first.task_generators

# Identify the user who created a specific Task Generator
TaskGenerator.first.created_by

Important Info Piece

This entity represents the pieces of critical information each User within a given Circle needs to be aware of. The list of Important Info Piece objects for a Circle will be consolidated under the 'about' tab, or something more meaningful such as 'Need To Know'.

It also has its own array (thanks to PostgreSQL) that contains the ids of the Users who have acknowledged the newly added piece of information.

Generator

rails g scaffold important_info_piece description:text category:references circle:references created_by_id:integer seen_by:integer

The migration will need changes to require the description field and add the array functionality for the seen_by attribute.

t.string :description, null: false
t.integer :seen_by, array: true, default: []

As usual, it also needs the foreign key constraint added.

add_foreign_key :important_info_pieces, :users, column: :created_by_id

Associations

belongs_to :category
belongs_to :circle
belongs_to :created_by, class_name: 'User'

This gives us the following functionality:

ImportantInfoPiece.create(
  description: 'Has fragile bones',
  category: Category.last,
  created_by: User.last,
  circle: current_circle
)

# retrieve an array of important info pieces for a specific circle.
circle = Circle.all[3]
circle.important_info_pieces

Post

This is the entity previously refered to as 'log'. I'm using Post as it is more descriptive of the entity's actual use. Currently, it represents some text and can be tagged as a 'medical' related post. The User who posted it and the time are also indicated.

Generator

rails g scaffold post description:text circle:references user:references medical:boolean

The obligatory changes to the migration file:

t.text :description, null: false
t.boolean :medical, default: false

Associations

Nice and simple.

belongs_to :circle
belongs_to :user

We now have the functionality to see all posts associated with a given Circle, and also, if need be, to display Post objects by a given user, regardless of circle.

Post.create(
  description: 'I love posting things.',
  circle: current_circle,
  user: current_user
)

# retrieve an array of posts for a given circle
Circle.all[14].posts

# retrive an array of posts for a specific user
User.find(first_name: 'Timmy').posts

Task

This entity represents the literal, single-time task that is to be performed. A single Task object can be created directly by a User or a by a TaskGenerator (if it is a reoccuring task).

Once marked complete by a user the Task object will remain editable (the only thing editable is the complete attribute) for a period of time (24 hours after the 'updated_at' timestamp). After that, the task is archived/deleted and a TaskReport object is generated. A Task object is also removed and reported if it expires.

Generator

rails g scaffold task description:text expiration_date:datetime completed:boolean completed_by_id:integer created_by_id:integer category:references task_generator:references circle:references

Migrations file alterations:

t.text :description, null: false
t.boolean :completed, default: false

# outside the change block
add_foreign_key :tasks, :users, column: :created_by_id
add_foreign_key :tasks, :users, column: :completed_by_id

Associations

belongs_to :category
belongs_to :task_generator, optional: true
belongs_to :circle
belongs_to :created_by, class_name: 'User'
belongs_to :completed_by, class_name: 'User', optional: true

The following functionality now exists:

Task.create(
  description: 'Clean the dishes',
  category: Category.first,
  circle: Circle.first,
  created_by: User.first
)

# retrieve an array of task objects for a given circle.
Circle.first.tasks

/////The following are unrefined////

User Report

This entity represents a User that is meant to persist beyond a User's account. In the case a User delete's his/her account, a record of the User's UUID and other critical data will be kept intact.

Thought needs to be given as what information needs to be exactly. What happens when a User edits their profile for example and changes their name or email?

rails g scaffold user_report uuid:string

Task Report

This entity is generated when a Task has been marked completed for 24 hours or it expires. Currently it will be a simple description of the task and if/when it was completed. The data associated with a Task needs to be stringified to prevent changes (eg. the User who completed the task needs to be have their name stringified so if they delete their account the data still remains).

rails g scaffold task_report description:text

Cirle Report

This entity represents a Circle that is meant to persist beyond the actual circle. It should reference the unique circle along with all Users involved.

Again, thought needs to be given to exactly what information needs to be saved and what happens when data is edited.

rails g scaffold circle_report uuid:string
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment