diff --git a/lib/generators/michel/view/templates/has_many_associations.erb b/lib/generators/michel/view/templates/has_many_time_slot_associations.erb similarity index 100% rename from lib/generators/michel/view/templates/has_many_associations.erb rename to lib/generators/michel/view/templates/has_many_time_slot_associations.erb diff --git a/lib/generators/michel/view/templates/resource_has_many_associations.erb b/lib/generators/michel/view/templates/resource_has_many_associations.erb new file mode 100644 index 0000000..121f189 --- /dev/null +++ b/lib/generators/michel/view/templates/resource_has_many_associations.erb @@ -0,0 +1,3 @@ + + has_many :<%=Michel.booking_class_symbol_plural%> + has_many :<%=Michel.availability_class_symbol_plural%> diff --git a/lib/generators/michel/view/view_generator.rb b/lib/generators/michel/view/view_generator.rb index b4aa92d..e05336e 100644 --- a/lib/generators/michel/view/view_generator.rb +++ b/lib/generators/michel/view/view_generator.rb @@ -6,6 +6,39 @@ class ViewGenerator < Rails::Generators::Base source_root File.expand_path("templates", __dir__) include Scenic + def check_resource_class + Rails::Generators.invoke("model", [ + Michel.resource_class_name, + "name:string", + "--skip" + ], destination_root: Rails.root, + behavior: behavior) + end + + def check_availability_class + Rails::Generators.invoke("model", [ + Michel.availability_class_name, + "timezone:string", + "weekday:integer", + "start_time:string", + "end_time:string", + "#{Michel.resource_class_underscore}:references", + "--skip" + ], destination_root: Rails.root, + behavior: behavior) + end + + def check_booking_class + Rails::Generators.invoke("model", [ + Michel.booking_class_name, + "start_time:datetime", + "duration:integer", + "#{Michel.resource_class_underscore}:references", + "--skip" + ], destination_root: Rails.root, + behavior: behavior) + end + def create_index_in_migration self.destination_root = Rails.root @@ -35,15 +68,20 @@ def create_sql_file end def add_associations_to_models - has_many_associations = template_content("has_many_associations.erb") - - inject_into_class "app/models/#{Michel.availability_class_underscore}.rb", Michel.availability_class_name, - has_many_associations - inject_into_class "app/models/#{Michel.resource_class_underscore}.rb", Michel.resource_class_name, - has_many_associations - belongs_to_associations = template_content("belongs_to_associations.erb") case behavior when :invoke + has_many_time_slot_associations = template_content("has_many_time_slot_associations.erb") + + inject_into_class "app/models/#{Michel.availability_class_underscore}.rb", Michel.availability_class_name, + has_many_time_slot_associations + inject_into_class "app/models/#{Michel.resource_class_underscore}.rb", Michel.resource_class_name, + has_many_time_slot_associations + + resource_has_many_associations = template_content("resource_has_many_associations.erb") + inject_into_class "app/models/#{Michel.resource_class_underscore}.rb", Michel.resource_class_name, + resource_has_many_associations + + belongs_to_associations = template_content("belongs_to_associations.erb") inject_into_class "app/models/available_time_slot.rb", "AvailableTimeSlot", belongs_to_associations end end diff --git a/lib/michel.rb b/lib/michel.rb index f33858e..f6e2b44 100644 --- a/lib/michel.rb +++ b/lib/michel.rb @@ -30,6 +30,10 @@ def self.booking_class_symbol @@booking_class_name.underscore.to_sym end + def self.booking_class_symbol_plural + @@booking_class_name.pluralize.underscore.to_sym + end + def self.booking_class_table_name @@booking_class_name.tableize end @@ -46,6 +50,10 @@ def self.availability_class_symbol @@availability_class_name.underscore.to_sym end + def self.availability_class_symbol_plural + @@availability_class_name.pluralize.underscore.to_sym + end + def self.availability_class_table_name @@availability_class_name.tableize end diff --git a/spec/example-app/app/models/appointment.rb b/spec/example-app/app/models/appointment.rb deleted file mode 100644 index b039aea..0000000 --- a/spec/example-app/app/models/appointment.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Appointment < ApplicationRecord - belongs_to :physician -end diff --git a/spec/example-app/app/models/physician.rb b/spec/example-app/app/models/physician.rb deleted file mode 100644 index 3cb50cf..0000000 --- a/spec/example-app/app/models/physician.rb +++ /dev/null @@ -1,4 +0,0 @@ -class Physician < ApplicationRecord - has_many :physician_availabilities - has_many :appointments -end diff --git a/spec/example-app/app/models/physician_availability.rb b/spec/example-app/app/models/physician_availability.rb deleted file mode 100644 index ce486cd..0000000 --- a/spec/example-app/app/models/physician_availability.rb +++ /dev/null @@ -1,3 +0,0 @@ -class PhysicianAvailability < ApplicationRecord - belongs_to :physician -end diff --git a/spec/example-app/db/migrate/20250829205200_create_physicians.rb b/spec/example-app/db/migrate/20250829205200_create_physicians.rb deleted file mode 100644 index b4f8d68..0000000 --- a/spec/example-app/db/migrate/20250829205200_create_physicians.rb +++ /dev/null @@ -1,8 +0,0 @@ -class CreatePhysicians < ActiveRecord::Migration[8.0] - def change - create_table :physicians do |t| - t.string :name - t.timestamps - end - end -end diff --git a/spec/example-app/db/migrate/20250829205205_create_appointments.rb b/spec/example-app/db/migrate/20250829205205_create_appointments.rb deleted file mode 100644 index ad976b9..0000000 --- a/spec/example-app/db/migrate/20250829205205_create_appointments.rb +++ /dev/null @@ -1,10 +0,0 @@ -class CreateAppointments < ActiveRecord::Migration[8.0] - def change - create_table :appointments do |t| - t.references :physician - t.datetime :start_time, null: false - t.integer :duration, null: false # minutes - t.timestamps - end - end -end diff --git a/spec/example-app/db/migrate/20250829205257_create_availabilities.rb b/spec/example-app/db/migrate/20250829205257_create_availabilities.rb deleted file mode 100644 index 17ac9e2..0000000 --- a/spec/example-app/db/migrate/20250829205257_create_availabilities.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreateAvailabilities < ActiveRecord::Migration[8.0] - def change - create_table :physician_availabilities do |t| - t.references :physician - t.integer :weekday, null: false # 1 = Monday, etc - t.string :start_time, null: false # '09:00' - t.string :end_time, null: false # '17:00' - t.string :timezone, null: false # e.g. 'UTC' - t.timestamps - end - end -end diff --git a/spec/example-app/db/schema.rb b/spec/example-app/db/schema.rb index 5c8b539..e5e32fd 100644 --- a/spec/example-app/db/schema.rb +++ b/spec/example-app/db/schema.rb @@ -10,33 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_08_29_205257) do +ActiveRecord::Schema[8.0].define(version: 0) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" - - create_table "appointments", force: :cascade do |t| - t.bigint "physician_id" - t.datetime "start_time", null: false - t.integer "duration", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["physician_id"], name: "index_appointments_on_physician_id" - end - - create_table "physician_availabilities", force: :cascade do |t| - t.bigint "physician_id" - t.integer "weekday", null: false - t.string "start_time", null: false - t.string "end_time", null: false - t.string "timezone", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["physician_id"], name: "index_physician_availabilities_on_physician_id" - end - - create_table "physicians", force: :cascade do |t| - t.string "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end end diff --git a/spec/generators/michel/view/view_generator_spec.rb b/spec/generators/michel/view/view_generator_spec.rb index 26a52c9..b3ef627 100644 --- a/spec/generators/michel/view/view_generator_spec.rb +++ b/spec/generators/michel/view/view_generator_spec.rb @@ -2,62 +2,113 @@ require "generators/michel/view/view_generator" RSpec.describe Michel::Generators::ViewGenerator, :generator do - before(:all) do - Michel.setup do |config| - config.resource_class_name = "Physician" - config.booking_class_name = "Appointment" - config.availability_class_name = "PhysicianAvailability" + context "When the resource, booking, and availability classes exist" do + before(:all) do + create_physician_models + Michel.setup do |config| + config.resource_class_name = "Physician" + config.booking_class_name = "Appointment" + config.availability_class_name = "PhysicianAvailability" + end + Rails::Generators.invoke("michel:view") + + ActiveRecord::MigrationContext.new(Rails.root.join("db/migrate")).migrate + Rails.autoloaders.main.reload end - Rails::Generators.invoke("michel:view") - ActiveRecord::MigrationContext.new(Rails.root.join("db/migrate")).migrate - Rails.autoloaders.main.reload - end + after(:all) do + ActiveRecord::MigrationContext.new(Rails.root.join("db/migrate")).rollback(5) + Rails::Generators.invoke("michel:view", [], behavior: :revoke) + Rails.autoloaders.main.reload + end - after(:all) do - ActiveRecord::MigrationContext.new(Rails.root.join("db/migrate")).rollback(2) - Rails::Generators.invoke("michel:view", [], behavior: :revoke) - end + it "generates available time slots" do + doctor = Physician.create!(name: "Dr. Seuss") + + PhysicianAvailability.create!( + physician: doctor, + weekday: Date.tomorrow.wday, # ensures it aligns with tomorrow + start_time: "09:00", + end_time: "10:00", + timezone: "UTC" + ) + + AvailableTimeSlot.refresh + slots = AvailableTimeSlot.all + + expect(slots).not_to be_empty + expect(slots.first.start_time.hour).to eq(9) + end + + it "excludes slots that overlap with a booking" do + doctor = Physician.create!(name: "Dr. Seuss") + PhysicianAvailability.create!( + physician: doctor, + weekday: Date.tomorrow.wday, # ensures it aligns with tomorrow + start_time: "09:00", + end_time: "10:00", + timezone: "UTC" + ) - it "generates available time slots" do - doctor = Physician.create!(name: "Dr. Seuss") + # Book a 30min slot at 9:00 + Appointment.create!( + physician: doctor, + start_time: Date.tomorrow.beginning_of_day + 9.hours, + duration: 30 + ) - PhysicianAvailability.create!( - physician: doctor, - weekday: Date.tomorrow.wday, # ensures it aligns with tomorrow - start_time: "09:00", - end_time: "10:00", - timezone: "UTC" - ) + AvailableTimeSlot.refresh - AvailableTimeSlot.refresh - slots = AvailableTimeSlot.all + # Now the 9:00 slot should not be available + slot_times = AvailableTimeSlot.pluck(:start_time) + expect(slot_times).not_to include(Date.tomorrow.beginning_of_day + 9.hours) + end + end - expect(slots).not_to be_empty - expect(slots.first.start_time.hour).to eq(9) + context "When the resource, booking, and availability do not exist" do + before(:all) do + Michel.setup do |config| + config.resource_class_name = "Provider" + config.booking_class_name = "Consult" + config.availability_class_name = "ProviderAvailability" + end + Rails::Generators.invoke("michel:view") + end + + after(:all) do + Rails::Generators.invoke("michel:view", [], behavior: :revoke) + end + + it "creates resource, booking, and availability classes" do + expect(Object.const_defined?(Michel.resource_class_name)) + expect(Object.const_defined?(Michel.booking_class_name)) + expect(Object.const_defined?(Michel.availability_class_name)) + end end - it "excludes slots that overlap with a booking" do - doctor = Physician.create!(name: "Dr. Seuss") - PhysicianAvailability.create!( - physician: doctor, - weekday: Date.tomorrow.wday, # ensures it aligns with tomorrow - start_time: "09:00", - end_time: "10:00", - timezone: "UTC" - ) - - # Book a 30min slot at 9:00 - Appointment.create!( - physician: doctor, - start_time: Date.tomorrow.beginning_of_day + 9.hours, - duration: 30 - ) - - AvailableTimeSlot.refresh - - # Now the 9:00 slot should not be available - slot_times = AvailableTimeSlot.pluck(:start_time) - expect(slot_times).not_to include(Date.tomorrow.beginning_of_day + 9.hours) + def create_physician_models + Rails::Generators.invoke("model", [ + "Physician", + "name:string", + "--skip" + ], destination_root: Rails.root) + + Rails::Generators.invoke("model", [ + "Appointment", + "start_time:datetime", + "duration:integer", + "physician:references", + "--skip" + ], destination_root: Rails.root) + + Rails::Generators.invoke("model", [ + "PhysicianAvailability", + "timezone:string", + "weekday:integer", + "start_time:string", + "end_time:string", + "physician:references", + "--skip" + ], destination_root: Rails.root) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3023dee..9f3ba4f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -15,6 +15,9 @@ config.expect_with :rspec do |c| c.syntax = :expect end + config.order = :random + Kernel.srand config.seed + config.around(:each) do |example| ActiveRecord::SchemaMigration .new(ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool)