From fb0267dd9145e4cd8907aaed5235b4276228c1d4 Mon Sep 17 00:00:00 2001 From: Mauricio Fierro Date: Tue, 23 Jul 2024 01:37:01 -0500 Subject: [PATCH] Allow sections to be unique within lessons only - Add migration to fix the unique index - Add scope to the uniqueness validation - Add user factory - Add user association to lesson factory - Lesson factory corrections - Add section factory - Add section model spec NOTE: We modify the start_time and end_time validations to provide a default value for lesson.duration_in_seconds because there's an issue with shoulda matchers and validations that refer to associated models according to this issue https://github.com/thoughtbot/shoulda-matchers/issues/1435 --- app/models/section.rb | 16 +++++++++++----- ...23030225_fix_section_name_uniqueness_index.rb | 6 ++++++ db/schema.rb | 4 ++-- spec/factories/lesson.rb | 5 +++-- spec/factories/section.rb | 16 ++++++++++++++++ spec/factories/user.rb | 7 +++++++ spec/models/section_spec.rb | 13 +++++++++++++ 7 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 db/migrate/20240723030225_fix_section_name_uniqueness_index.rb create mode 100644 spec/factories/section.rb create mode 100644 spec/factories/user.rb create mode 100644 spec/models/section_spec.rb diff --git a/app/models/section.rb b/app/models/section.rb index c653823..c2cc743 100644 --- a/app/models/section.rb +++ b/app/models/section.rb @@ -8,7 +8,8 @@ class Section < ApplicationRecord class_name: "VideoPoint", mapping: [ %w(end_time_hour hour), %w(end_time_minute minute), %w(end_time_second second) ] - validates :name, presence: true + validates :name, presence: true, uniqueness: { scope: :lesson_id } + validates :start_time_hour, :start_time_minute, :start_time_second, presence: true, numericality: { @@ -29,16 +30,21 @@ class Section < ApplicationRecord less_than_or_equal_to: 59 } + # Due to this issue in shoulda matchers library we'll have to add a default max value + # https://github.com/thoughtbot/shoulda-matchers/issues/1435 validates :start_time, numericality: { - less_than: -> (section) { section.lesson.duration_in_seconds }, - message: -> (object, data) { "must be less than #{object.lesson.duration_in_seconds}" } + less_than: -> (section) { section.lesson&.duration_in_seconds || 9999.0 }, + message: -> (object, data) { "must be less than #{object.lesson.duration_in_seconds}" }, + allow_nil: true } + # Due to this issue in shoulda matchers library we'll have to add a default max value + # https://github.com/thoughtbot/shoulda-matchers/issues/1435 validates :end_time, numericality: { - less_than_or_equal_to: -> (section) { section.lesson.duration_in_seconds }, - message: -> (object, data) { "must be less than #{object.lesson.duration_in_seconds}" } + less_than_or_equal_to: -> (section) { section.lesson&.duration_in_seconds || 9999.0 }, + message: -> (object, data) { "must be less than #{object.lesson.duration_in_seconds}" }, } validates :end_time, diff --git a/db/migrate/20240723030225_fix_section_name_uniqueness_index.rb b/db/migrate/20240723030225_fix_section_name_uniqueness_index.rb new file mode 100644 index 0000000..8db2221 --- /dev/null +++ b/db/migrate/20240723030225_fix_section_name_uniqueness_index.rb @@ -0,0 +1,6 @@ +class FixSectionNameUniquenessIndex < ActiveRecord::Migration[7.0] + def change + remove_index :sections, :name + add_index :sections, [:name, :lesson_id], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index c939152..4c4b036 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_07_04_034110) do +ActiveRecord::Schema[7.0].define(version: 2024_07_23_030225) do create_table "flipper_features", force: :cascade do |t| t.string "key", null: false t.datetime "created_at", null: false @@ -67,7 +67,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["lesson_id"], name: "index_sections_on_lesson_id" - t.index ["name"], name: "index_sections_on_name", unique: true + t.index ["name", "lesson_id"], name: "index_sections_on_name_and_lesson_id", unique: true end create_table "users", force: :cascade do |t| diff --git a/spec/factories/lesson.rb b/spec/factories/lesson.rb index 65c03db..0ec2630 100644 --- a/spec/factories/lesson.rb +++ b/spec/factories/lesson.rb @@ -3,10 +3,11 @@ name do "#{Faker::Music::RockBand.song} - #{Faker::Music::RockBand.name}" end - video_url { Faker::Internel.url(host: 'youtu.be') } + video_url { Faker::Internet.url(host: "youtu.be", scheme: "https") } duration_in_seconds do - Faker::Number.between(from: 0.0, to: 5.minutes.seconds.to_f) + Faker::Number.between(from: 0, to: 5.minutes.seconds) end instrument + user end end diff --git a/spec/factories/section.rb b/spec/factories/section.rb new file mode 100644 index 0000000..916a39e --- /dev/null +++ b/spec/factories/section.rb @@ -0,0 +1,16 @@ +FactoryBot.define do + factory :section do + lesson + sequence(:name) { |n| "Section #{n}" } + + start_time_hour { 0 } + start_time_minute { 0 } + start_time_second { 1 } + + end_time_hour { 0 } + end_time_minute { 0 } + end_time_second { Faker::Number.between(from: 1, to: 59 ) } + + playback_speed { 0.5.step(by: 0.5, to: 2.0).to_a.sample } + end +end diff --git a/spec/factories/user.rb b/spec/factories/user.rb new file mode 100644 index 0000000..8f6d177 --- /dev/null +++ b/spec/factories/user.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :user do + sequence(:email) { |n| "user#{n}@example.com" } + password { "password123" } + confirmed_at { Date.yesterday } + end +end diff --git a/spec/models/section_spec.rb b/spec/models/section_spec.rb new file mode 100644 index 0000000..854b488 --- /dev/null +++ b/spec/models/section_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +describe Section, type: :model do + subject { create(:section) } + + describe 'associations' do + it { should belong_to(:lesson) } + end + + describe 'validations' do + it { should validate_uniqueness_of(:name).scoped_to(:lesson_id) } + end +end