Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

has_many :<%=Michel.booking_class_symbol_plural%>
has_many :<%=Michel.availability_class_symbol_plural%>
52 changes: 45 additions & 7 deletions lib/generators/michel/view/view_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions lib/michel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 0 additions & 3 deletions spec/example-app/app/models/appointment.rb

This file was deleted.

4 changes: 0 additions & 4 deletions spec/example-app/app/models/physician.rb

This file was deleted.

3 changes: 0 additions & 3 deletions spec/example-app/app/models/physician_availability.rb

This file was deleted.

This file was deleted.

10 changes: 0 additions & 10 deletions spec/example-app/db/migrate/20250829205205_create_appointments.rb

This file was deleted.

This file was deleted.

28 changes: 1 addition & 27 deletions spec/example-app/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
147 changes: 99 additions & 48 deletions spec/generators/michel/view/view_generator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading