This is the entity that represents the individual who has the ability to 'login' using a device.
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
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
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.
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.
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"
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.
rails g scaffold role name:string
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.
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
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
.
rails g scaffold position role:references circle:references user:references
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
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.
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:
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: "[email protected]"
)
# recipient DOES exist
recipient = User.last
Invitation.create(
position: position,
sender: user,
email: recipient.email,
recipient: recipient
)
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.
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
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
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.
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
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
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.
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
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
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.
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
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////
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
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
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