Current section

Files

Jump to
ami lib ami lesson.ex
Raw

lib/ami/lesson.ex

defmodule Ami.Lesson do
use Ecto.Schema
import Ecto.{
Query,
Changeset
}
alias Ami.{
Tag,
Repo,
Unit,
Quiz,
Post,
Video,
Score,
Course,
Resource,
UserAnswer,
UnitLesson,
LessonResource,
LessonActivity,
LessonMainResource,
ResourceAttachment
}
schema "lessons" do
@timestamps_opts [type: :naive_datetime_usec]
field(:title, :string)
field(:description, :string)
field(:created_at, :utc_datetime_usec)
field(:updated_at, :utc_datetime_usec)
field(:paid, :boolean)
field(:sequential_tags, :string, virtual: true)
field(:with_activity, :boolean, default: false, virtual: true)
# belongs_to(:unit, Unit)
has_many(:unit_lessons, UnitLesson)
has_many(:units, through: [:unit_lessons, :unit])
has_one(:main_resource, LessonMainResource)
has_one(:main_resource_attachment, through: [:main_resource, :resource_attachment])
has_one(:lesson_activity, LessonActivity)
has_one(:lesson_activity_attachment, through: [:lesson_activity, :resource_attachment])
has_many(:lessons_resources, LessonResource)
has_many(:resource_attachments, through: [:lessons_resources, :resource_attachment])
many_to_many(:tags, Tag, join_through: "lessons_tags", on_replace: :delete)
# In Rails we had a polymorphic association for this which is not implemented in Ecto
# Scope is also not possible in Ecto so check function with_preloaded_videos function for more information.
has_many(:videos, Video, foreign_key: :videoable_id)
# Also a polymorphic association
has_many(:resources, Resource, foreign_key: :resourceable_id)
has_many(:posts, Post)
has_one(:quiz, Quiz)
end
@doc """
Because of better design patterns implemented by Ecto we cannot return lesson.videos to return correct videos.
preload_videos implement a pattern which will return lesson.videos to return only videos with videoable_type == "Lesson" for the given lesson
"""
def preload_videos(struct), do: Video.preload_videos(struct, "Lesson")
@doc """
Pattern to fetch resources as lesson.resources with resourceable_type == "Lesson"
"""
def preload_resources(struct), do: Resource.preload_resources(struct, "Lesson")
@doc """
Check if the lesson is completed or not
"""
def completed?(lesson, enrollment) do
lesson = Repo.preload(lesson, [:quiz])
if lesson.quiz do
if !lesson.quiz.is_final do
find(Score, lesson.quiz.id, enrollment.course_period_id, enrollment.user_id, enrollment.id) != nil
else
find(Score, lesson.quiz.id, enrollment.course_period_id, enrollment.user_id, enrollment.id) != nil && enrollment.assignment_status == "accepted"
end
end
end
# Helper to find User Answers or Score with smiliar arguments
defp find(module, quiz_id, course_period_id, user_id) do
Repo.get_by(module, quiz_id: quiz_id, course_period_id: course_period_id, user_id: user_id)
end
defp find(module, quiz_id, course_period_id, user_id, enrollment_id) do
Repo.get_by(module, quiz_id: quiz_id, course_period_id: course_period_id, user_id: user_id, enrollment_id: enrollment_id)
end
@doc """
Helper checking if a lesson has got an 'Activity' resource
"""
def has_activity?(lesson) do
lesson = Repo.preload(lesson, :lesson_activity)
!is_nil(lesson.lesson_activity)
end
def changeset(%__MODULE__{} = lesson, params \\ %{}) do
lesson
|> cast(params, [:title, :description, :sequential_tags])
|> parse_tags
|> cast_assoc(:tags)
|> cast_assoc(:unit_lessons)
|> cast_assoc(:main_resource)
|> cast_assoc(:lesson_activity)
|> cast_assoc(:lessons_resources)
end
def changeset_with_quiz(%__MODULE__{} = lesson, params \\ %{}) do
lesson
|> cast(params, [:title, :description, :sequential_tags])
|> parse_tags
|> cast_assoc(:tags)
|> cast_assoc(:quiz)
|> cast_assoc(:unit_lessons)
|> cast_assoc(:main_resource)
|> cast_assoc(:lesson_activity)
|> cast_assoc(:lessons_resources)
end
def conditionaly_cast_assoc(changeset, :lesson_activity) do
if is_nil(changeset.params["lesson_activity"]["resource_attachment"]["file"]) do
changeset
else
cast_assoc(changeset, :lesson_activity)
end
end
def parse_tags(changeset) do
if is_nil(changeset.params["sequencial_tags"]) do
changeset
else
stringify_tags = changeset.params["sequencial_tags"]
tags =
String.split(stringify_tags, ", ", trim: true)
|> Enum.map(fn tag -> find_or_build_tag(tag) end)
put_change(changeset, :tags, tags)
end
end
def struct_for_lesson_wizard() do
%__MODULE__{
unit_lessons: [%UnitLesson{unit: %Unit{}}],
lessons_resources: [
%LessonResource{resource_attachment: %ResourceAttachment{}},
%LessonResource{resource_attachment: %ResourceAttachment{}},
%LessonResource{resource_attachment: %ResourceAttachment{}},
%LessonResource{resource_attachment: %ResourceAttachment{}},
%LessonResource{resource_attachment: %ResourceAttachment{}},
%LessonResource{resource_attachment: %ResourceAttachment{}},
%LessonResource{resource_attachment: %ResourceAttachment{}}
]
}
end
def find_or_build_tag(tag_name) do
Tag.find_or_build_tag(tag_name)
end
def search(term) do
course_query = from(c in Course, select: [:title])
results =
Repo.all(
from l in __MODULE__,
left_join: t in assoc(l, :tags),
left_join: ul in assoc(l, :unit_lessons),
left_join: u in assoc(ul, :unit),
left_join: c in assoc(u, :course),
where: ilike(l.title, ^"%#{term}%"),
or_where: ilike(t.name, ^"%#{term}%"),
distinct: l.id,
preload: [
units: {u, [course: ^course_query]}
]
)
|> Enum.map(fn lesson ->
courses = Enum.map(lesson.units, fn unit -> unit.course.title end)
Map.put_new(lesson, :courses, courses)
end)
{:ok, results}
end
end