Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can create new courses #15148

Merged
merged 3 commits into from
May 17, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 29 additions & 3 deletions dashboard/app/controllers/courses_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class CoursesController < ApplicationController
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auth for modify actions?

before_action :require_levelbuilder_mode, except: [:index, :show]

def index
@recent_courses = current_user.try(:recent_courses)
@is_teacher = !!(current_user && current_user.teacher?)
Expand All @@ -8,9 +10,16 @@ def index
end

def show
course_name = params[:course_name].tr('-', '_').titleize
course = Course.find_by_name(course_name)
raise ActiveRecord::RecordNotFound unless course
course = Course.find_by_name(params[:course_name])
unless course
# PLC courses have different ways of getting to name. ideally this goes
# away eventually
course_name = params[:course_name].tr('-', '_').titleize
course = Course.find_by_name!(course_name)
# only support this alternative course name for plc courses
raise ActiveRecord::RecordNotFound unless course.plc_course
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary — find_by_name! should raise if the course isn't found.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe you are right

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_by_name! raises if the course is not found, but we also want to raise if we found a course, but it is a regular course rather than a plc_course

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Could change find_by_name! -> find_by_name for clarity.

end

if course.plc_course
authorize! :show, Plc::UserCourseEnrollment
user_course_enrollments = [Plc::UserCourseEnrollment.find_by(user: current_user, plc_course: course.plc_course)]
Expand All @@ -20,4 +29,21 @@ def show

render 'show', locals: {course: course}
end

def new
render 'new'
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We should standardize on whether we want to include these boilerplate action definitions if they are only rendering the default view. I know you prefer explicit, but Rails is CoC by default 😛.


def create
course = Course.new(name: params.require(:course).require(:name))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth pulling this out to a private course_params method, and re-using in the show action? For example: ScriptsController#script_params.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They use two different sets of params? show takes params[:course_name], where as here we have params[:course][:name]

if course.save
redirect_to action: :edit, course_name: course.name
else
render 'new', locals: {error_messages: course.errors.full_messages}
Copy link
Contributor

@aoby aoby May 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A common way of doing this is to set @course, and have the form in the view be form_for @course. That way any params are saved between attempts, and errors are already on the @course object and don't need to be passed separately.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been trying to avoid using instance variables in my views, as I find them much more difficult to follow than explicitly passing things in via locals. This might be a case where there's a good argument for doing it the way you say.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or if you prefer it to be explicit, pass course as a local, do form_for course, and inspect course.errors

end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this action is missing test coverage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how interesting this method is to test. I might add a quick and dirty test in a future PR.


def edit
render text: 'Not yet implemented'
end
end
26 changes: 26 additions & 0 deletions dashboard/app/models/course.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,32 @@ class Course < ApplicationRecord
has_one :plc_course, class_name: 'Plc::Course'
has_many :course_scripts, -> {order('position ASC')}

validates :name,
presence: true,
uniqueness: {case_sensitive: false}

class CourseNameValidator < ActiveModel::Validator
def validate(course)
return if course.plc_course

unless /\A[a-z0-9\-]+\z/ =~ course.name
course.errors[:base] << 'can only contain lowercase letters, numbers and dashes'
end
end
end

validates_with CourseNameValidator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this format instead of validates :name (with unless: :plc_course)? It looks like there's common functionality in Script we could maybe combine into a single concern. If we want to start standardizing on this format for all names that appear in URLs...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also for custom validators that are only used in a single, it's simpler to just define a method (example). These ActiveModel::Validator classes are typically used for validations shared across models, for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're way is much better than mine. I will try that approach.


# As we read and write to files with the course name, to prevent directory
# traversal (for security reasons), we do not allow the name to start with a
# tilde or dot or contain a slash.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as in Script, this is redundant with the above name validation. If we really want defense-in-depth maybe we should pull this validator out as a concern?

validates :name,
presence: true,
format: {
without: /\A~|\A\.|\//,
message: 'cannot start with a tilde or dot or contain slashes'
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have this de-duped against the name validation in Script.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a concern to do this may be easier to do than I thought. I will explore doing this in a follow on PR


def summarize
{
name: name,
Expand Down
7 changes: 5 additions & 2 deletions dashboard/app/models/script.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,11 @@ class Script < ActiveRecord::Base

def generate_plc_objects
if professional_learning_course?
course = Course.find_or_create_by!(name: professional_learning_course) do |new_course|
Plc::Course.create!(course: new_course)
course = Course.find_by_name(professional_learning_course)
unless course
course = Course.new(name: professional_learning_course)
course.plc_course = Plc::Course.create!(course: course)
course.save!
end
unit = Plc::CourseUnit.find_or_initialize_by(script_id: id)
unit.update!(
Expand Down
17 changes: 17 additions & 0 deletions dashboard/app/views/courses/new.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
- error_messages = local_assigns[:error_messages]

- if error_messages.try(:any?)
#error_explanation
%h2
= pluralize(error_messages.count, 'error')
prohibited this course from being saved:
%ul
- error_messages.each do |msg|
%li= msg

%h1= t('crud.new_model', model: Course.model_name.human)
= form_for(:course, {url: courses_path}) do |f|
.field
= f.label :name
= f.text_field :name
%button.btn.btn-primary{type: 'submit', style: 'margin: 0'} Save Changes
26 changes: 14 additions & 12 deletions dashboard/test/controllers/courses_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,35 @@ class CoursesControllerTest < ActionController::TestCase

setup do
@teacher = create :teacher
plc_course = create :plc_course, name: "My PLC"
sign_in @teacher

plc_course = create :plc_course, name: 'My PLC'
@course_plc = plc_course.course
@course_regular = create :course, name: 'non plc course'
@course_regular = create :course, name: 'non-plc-course'
end

test "plc courses get sent to user_course_enrollments_controller" do
sign_in @teacher

get :show, params: {course_name: @course_plc.name}
assert_template 'plc/user_course_enrollments/index'
end

get :show, params: {course_name: 'my-plc'}
test "plc course names get titleized" do
get :show, params: {course_name: 'my_plc'}
assert_template 'plc/user_course_enrollments/index'
end

test "regular courses get sent to index" do
sign_in @teacher

test "regular courses get sent to show" do
get :show, params: {course_name: @course_regular.name}
assert_template 'courses/show'
end

get :show, params: {course_name: 'non-plc-course'}
assert_template 'courses/show'
test "regular courses do not get titlized" do
assert_raises ActiveRecord::RecordNotFound do
get :show, params: {course_name: 'non_plc_course'}
end
end

test "non exist course throws" do
sign_in @teacher
test "non existant course throws" do
assert_raises ActiveRecord::RecordNotFound do
get :show, params: {course_name: 'nosuchcourse'}
end
Expand Down
6 changes: 4 additions & 2 deletions dashboard/test/factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -529,9 +529,11 @@
end
factory :plc_course, class: 'Plc::Course' do
transient do
name 'MyString'
name 'plccourse'
end
after(:build) do |plc_course, evaluator|
create(:course, name: evaluator.name, plc_course: plc_course)
end
course {create(:course, name: name)}
end

factory :level_group, class: LevelGroup do
Expand Down
24 changes: 21 additions & 3 deletions dashboard/test/models/course_test.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
require 'test_helper'

class CourseTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
test "should allow valid course names" do
create(:course, name: 'valid-name')
end

test "should not allow uppercase letters in course name" do
assert_raises ActiveRecord::RecordInvalid do
create(:course, name: 'UpperCase')
end
end

test "should not allow spaces in course name" do
assert_raises ActiveRecord::RecordInvalid do
create(:course, name: 'spaced out')
end
end

test "should allow uppercase letters if it is a plc course" do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we tracking the debt to unify & redirect professional learning courses that don't match the format?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's somewhat less important for these to be unified, given that PLC courses aren't being serialized to a file based on their name (unlike regular courses in the near future).

course = Course.new(name: 'PLC Course')
course.plc_course = Plc::Course.new(course: course)
course.save!
end
end