Models, Associations, and more
In this guide, you will get a brief introduction to Rails models, which are wrappers around their database components. Models are the data that are represented by your app, and because they are purely data, they should not know anything about their representation in the views.
Rails utilizes relational models on top of a RDBMS (Relational DataBase Management System), which for the most part we will be using PostgreSQL. This means that within your application, certain models will be related to each other by different kinds of relationships (discussed more in the associations section). For example, in an app designed to serve online courses, the Student model may have many Courses, and Courses may have many Sections. All of these relationships will be to be specified by you, and the effect of achieving this is that you'll have a much more scalable application and more easily manageable resources.
Models
A model scaffold in Rails can be generated using the console. It is recommended to create models this way as it will also set up the proper migrations that you can immediately run afterwards, as well as test files that you can write later to verify your model behavior.
Let's generate a basic model called Course, that has a name and a description.
rails g model Course name:string description:string
This creates a migration that adds the courses table into your application schema (which you can find in schema.rb) and adds the appropriate name and description fields. Note that these fields are modified through migrations, and they are not usually not specified in your course.rb model file. An exception to this is when declaring another model to be an association, in which the association is specified, giving access to the associated model.
Here is an example of a Course model in a real Rails application :)
# == Schema Information
#
# Table name: courses
#
# id :integer not null, primary key
# name :string default("Course Name")
# created_at :datetime not null
# updated_at :datetime not null
# description :string default("This is a course description.")
# is_published :boolean default(FALSE)
#
class Course < ActiveRecord::Base
validates :name, :description, presence: true
has_many :sections, -> { order(position: :asc) }
has_many :code_courses
has_many :codes, through: :code_courses
has_many :course_requests
has_many :requests, through: :course_requests
has_one :photo, as: :parent, dependent: :destroy
accepts_nested_attributes_for :photo
scope :is_published, -> { where(:is_published => true) }
def is_enrolled?(user)
user.instance_of?(Admin) ||
StudentCourse.find_by({ course_id: id, student_id: user.id})
end
def is_self_paced?(user)
StudentCourse.find_by({ course_id: id, student_id: user.id, self_paced: true })
end
def current_subsection(user)
sections.each do |section|
section.subsections.each do |subsection|
if !subsection.is_complete?(user)
return subsection
end
end
end
sections.last.subsections.last
end
def progress(user)
total_components = 0.0
completed_components = 0.0
sections.map(&:subsections)
.flatten
.map(&:components)
.flatten.each do |component|
total_components += 1
completed_components += 1 if component.is_complete?(user)
end
puts completed_components
puts total_components
return total_components == 0 ?
0 : (completed_components / total_components * 100).round
end
end
Note that the associations to other models are delineated in the Course class via methods like has_many and has_one. Also, note that the model contains helper methods like current_subsection(user) to get the earliest subsection that the user has not already completed and progress(user) to get the percentage progress that a user has in this course. These methods rely only on the data and not their representation, so they are appropriate to include as part of your models if necessary.
Another thing to notice is that the first line within the class:
validates :name, :description, presence: true
is an example of a model validation checking the presence of certain attributes, which we will talk more about in the next section.
Validations
Validations are a way to ensure that the data you are putting into your application have a defined format and existence. For example, you would not want a user to be able to sign up for your application without a username or password, and you certainly wouldn't want your users to be creating accounts with invalid emails. The most basic type of validation is a validation on presence:
validates :name, presence: true
There are plenty of other things you can do with basic validations, such as checking uniqueness and inclusion:
validates :email, uniqueness: true
validates :activity, inclusion: {:in => Member.activities}
You can get even more fancy with validations, and use your own method to validate some property:
validate :correct_parent_password, on: :create
# This method checks that the user's inputted parent_password is
# correct before allowing the user to be created.
def correct_parent_password
unless self.parent.valid_password?(parent_password)
errors.add(:parent_password, 'for this parent is incorrect.')
end
end
To learn more about Rails validations, visit this guide.
Associations Between Models
Associations are an essential part of understanding Rails models and building your applications to be scalable and manageable. There are six different types of associations that Rails supports, and you will commonly use three or four of them. Follow this great guide here, which goes into great detail about different associations and how to use them.