From 3ef19093ad7a55975079541a528a078cd2debfda Mon Sep 17 00:00:00 2001
From: Sarah-Jeanne <>
Date: Thu, 7 Mar 2024 21:11:24 -0500
Subject: [PATCH 1/4] created label model for files

---
 app/models/label.rb                        |  2 ++
 db/migrate/20240308014217_create_labels.rb | 10 ++++++++++
 db/schema.rb                               |  9 ++++++++-
 spec/factories/labels.rb                   |  5 +++++
 spec/models/label_spec.rb                  |  5 +++++
 5 files changed, 30 insertions(+), 1 deletion(-)
 create mode 100644 app/models/label.rb
 create mode 100644 db/migrate/20240308014217_create_labels.rb
 create mode 100644 spec/factories/labels.rb
 create mode 100644 spec/models/label_spec.rb

diff --git a/app/models/label.rb b/app/models/label.rb
new file mode 100644
index 00000000..73f1ac6e
--- /dev/null
+++ b/app/models/label.rb
@@ -0,0 +1,2 @@
+class Label < ApplicationRecord
+end
diff --git a/db/migrate/20240308014217_create_labels.rb b/db/migrate/20240308014217_create_labels.rb
new file mode 100644
index 00000000..be1687ef
--- /dev/null
+++ b/db/migrate/20240308014217_create_labels.rb
@@ -0,0 +1,10 @@
+class CreateLabels < ActiveRecord::Migration[7.1]
+  def change
+    create_table :labels do |t|
+      t.string :name, null: false
+      t.string :color, null: false
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index ff309a73..2864c48a 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: 2023_11_27_001950) do
+ActiveRecord::Schema[7.1].define(version: 2024_03_08_014217) do
   create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
     t.string "name", null: false
     t.string "record_type", null: false
@@ -47,6 +47,13 @@
     t.datetime "archived_at", precision: nil
   end
 
+  create_table "labels", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+    t.string "name", null: false
+    t.string "color", null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+  end
+
   create_table "printer_jobs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
     t.string "name"
     t.bigint "printer_id", null: false
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
new file mode 100644
index 00000000..016c08c4
--- /dev/null
+++ b/spec/factories/labels.rb
@@ -0,0 +1,5 @@
+FactoryBot.define do
+  factory :label do
+    
+  end
+end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
new file mode 100644
index 00000000..399031db
--- /dev/null
+++ b/spec/models/label_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Label, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end

From ebdcdbc88d8ae52a440e7663175cec8727497cbe Mon Sep 17 00:00:00 2001
From: Sarah-Jeanne <>
Date: Mon, 11 Mar 2024 11:37:48 -0400
Subject: [PATCH 2/4] adding validation, factory and unit tests for labels

---
 app/models/label.rb       |  2 ++
 spec/factories/labels.rb  |  3 ++-
 spec/models/label_spec.rb | 43 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 46 insertions(+), 2 deletions(-)

diff --git a/app/models/label.rb b/app/models/label.rb
index 73f1ac6e..b51c33c2 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -1,2 +1,4 @@
 class Label < ApplicationRecord
+    validates :name, presence: true, uniqueness: true
+    validates :color, presence: true, format: { with: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\Z/, message: "color should be an hex" }
 end
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index 016c08c4..84c6cbc4 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -1,5 +1,6 @@
 FactoryBot.define do
   factory :label do
-    
+    name { 'My red label' }
+    color { '#ff0000' }
   end
 end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 399031db..84509940 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -1,5 +1,46 @@
 require 'rails_helper'
 
 RSpec.describe Label, type: :model do
-  pending "add some examples to (or delete) #{__FILE__}"
+  let(:label) { build(:label) }
+
+  it 'has a valid factory' do
+    expect(label).to be_valid
+  end
+
+  describe '#name' do
+    subject { label.name }
+
+    it { is_expected.to eq 'My red label' }
+  end
+
+  describe '#color' do
+    subject { label.color }
+
+    it { is_expected.to eq '#ff0000' }
+  end
+
+  it 'has a valid name' do
+    expect(Label.new(name: nil, color: '#ffffff')).to_not be_valid
+    expect(Label.new(name: '', color: '#ffffff')).to_not be_valid
+    expect(Label.new(color: '#ffffff')).to_not be_valid
+  end
+
+  it 'has a unique name' do
+    Label.create(name: 'My yellow label', color: '#0000ff') # same as create(:label, name: 'My yellow label', color: '#0000ff')
+    duplicate_label = Label.new(name: 'My yellow label', color: '#00f')
+    expect(duplicate_label).to_not be_valid
+  end
+
+  it 'has a valid color format' do
+    expect(Label.new(name: 'my label')).to_not be_valid
+    expect(Label.new(name: 'my label', color: '')).to_not be_valid
+    expect(Label.new(name: 'my label', color: nil)).to_not be_valid
+    expect(Label.new(name: 'my label', color: 'green')).to_not be_valid
+    expect(Label.new(name: 'my label', color: '123456')).to_not be_valid
+    expect(Label.new(name: 'my label', color: '#1234')).to_not be_valid
+    expect(Label.new(name: 'my label', color: '#gg00gg')).to_not be_valid
+    expect(Label.new(name: 'my label', color: 'rgb(0, 155, 0)')).to_not be_valid
+    expect(Label.new(name: 'my label', color: '#ff00ff')).to be_valid
+    expect(Label.new(name: 'my label', color: '#f0f')).to be_valid
+  end
 end

From 184b5960eb5f6b5bae5aa1768f61532b6b0a1dc7 Mon Sep 17 00:00:00 2001
From: Sarah-Jeanne <>
Date: Tue, 12 Mar 2024 20:17:20 -0400
Subject: [PATCH 3/4] added unit tests for FileLabel

---
 app/models/file_label.rb                          |  8 ++++++++
 app/models/file_record.rb                         |  2 ++
 app/models/label.rb                               |  3 +++
 db/migrate/20240311162807_create_file_labels.rb   | 10 ++++++++++
 db/schema.rb                                      | 13 ++++++++++++-
 .../{file_manager/files.rb => file_records.rb}    |  4 ++++
 spec/factories/labels.rb                          |  4 ++++
 .../file_spec.rb => file_record_spec.rb}          | 15 +++++++++++++++
 spec/models/label_spec.rb                         | 15 +++++++++++++++
 9 files changed, 73 insertions(+), 1 deletion(-)
 create mode 100644 app/models/file_label.rb
 create mode 100644 db/migrate/20240311162807_create_file_labels.rb
 rename spec/factories/{file_manager/files.rb => file_records.rb} (89%)
 rename spec/models/{file_manager/file_spec.rb => file_record_spec.rb} (86%)

diff --git a/app/models/file_label.rb b/app/models/file_label.rb
new file mode 100644
index 00000000..b1e7125b
--- /dev/null
+++ b/app/models/file_label.rb
@@ -0,0 +1,8 @@
+class FileLabel < ApplicationRecord
+  belongs_to :file_record
+  belongs_to :label
+
+  validates :file_record, presence: true
+  validates :label, presence: true
+  validates :file_record_id, uniqueness: { scope: :label_id }
+end
diff --git a/app/models/file_record.rb b/app/models/file_record.rb
index c4c72cc1..5567ef3e 100644
--- a/app/models/file_record.rb
+++ b/app/models/file_record.rb
@@ -3,6 +3,8 @@
 class FileRecord < ApplicationRecord
   has_one_attached :file
   has_many :jobs, class_name: 'Printer::Job', as: :executable, dependent: :nullify
+  has_many :file_labels
+  has_many :labels, through: :file_labels
 
   validates :file, presence: true
 
diff --git a/app/models/label.rb b/app/models/label.rb
index b51c33c2..34832391 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -1,4 +1,7 @@
 class Label < ApplicationRecord
     validates :name, presence: true, uniqueness: true
     validates :color, presence: true, format: { with: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\Z/, message: "color should be an hex" }
+
+    has_many :file_labels
+    has_many :file_records, through: :file_labels
 end
diff --git a/db/migrate/20240311162807_create_file_labels.rb b/db/migrate/20240311162807_create_file_labels.rb
new file mode 100644
index 00000000..9e2cdfdc
--- /dev/null
+++ b/db/migrate/20240311162807_create_file_labels.rb
@@ -0,0 +1,10 @@
+class CreateFileLabels < ActiveRecord::Migration[7.1]
+  def change
+    create_table :file_labels do |t|
+      t.references :file_record, null: false, foreign_key: true
+      t.references :label, null: false, foreign_key: true
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2864c48a..ca83645f 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.1].define(version: 2024_03_08_014217) do
+ActiveRecord::Schema[7.1].define(version: 2024_03_11_162807) do
   create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
     t.string "name", null: false
     t.string "record_type", null: false
@@ -39,6 +39,15 @@
     t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
   end
 
+  create_table "file_labels", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+    t.bigint "file_record_id", null: false
+    t.bigint "label_id", null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["file_record_id"], name: "index_file_labels_on_file_record_id"
+    t.index ["label_id"], name: "index_file_labels_on_label_id"
+  end
+
   create_table "file_records", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
     t.string "notes"
     t.datetime "created_at", null: false
@@ -75,5 +84,7 @@
   end
 
   add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
+  add_foreign_key "file_labels", "file_records"
+  add_foreign_key "file_labels", "labels"
   add_foreign_key "printer_jobs", "printers"
 end
diff --git a/spec/factories/file_manager/files.rb b/spec/factories/file_records.rb
similarity index 89%
rename from spec/factories/file_manager/files.rb
rename to spec/factories/file_records.rb
index 4d49290d..4b519504 100644
--- a/spec/factories/file_manager/files.rb
+++ b/spec/factories/file_records.rb
@@ -19,5 +19,9 @@
         )
       end
     end
+
+    trait(:with_labels) {
+      labels { build_list :label, 1 }
+    }
   end
 end
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index 84c6cbc4..9a1d8821 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -2,5 +2,9 @@
   factory :label do
     name { 'My red label' }
     color { '#ff0000' }
+
+    trait(:with_files) {
+      file_records { build_list :file_record, 1 }
+    }
   end
 end
diff --git a/spec/models/file_manager/file_spec.rb b/spec/models/file_record_spec.rb
similarity index 86%
rename from spec/models/file_manager/file_spec.rb
rename to spec/models/file_record_spec.rb
index aebbd01d..c8e0c18a 100644
--- a/spec/models/file_manager/file_spec.rb
+++ b/spec/models/file_record_spec.rb
@@ -7,6 +7,7 @@
 
   it 'has a valid factory' do
     expect(file).to be_valid
+    expect(build(:file_record, :with_labels)).to be_valid
   end
 
   describe '#filename' do
@@ -136,4 +137,18 @@
                             }.from(Rails.root.join('spec/fixture_files/test.gcode').read).to(content)
     end
   end
+
+  describe '#labels' do
+    subject { build(:file_record, :with_labels).labels }
+
+    it { is_expected.to_not be_nil }
+    it { is_expected.to_not be_empty }
+
+    it 'does not have duplicate labels associated to a single file record' do
+      file_record = create(:file_record, :with_labels)
+      file_record.labels.build(file_records: [file_record])
+
+      expect(file_record).to be_invalid # (same as to_not be_valid)
+    end
+  end
 end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 84509940..70f01e5c 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -5,6 +5,7 @@
 
   it 'has a valid factory' do
     expect(label).to be_valid
+    expect(build(:label, :with_files)).to be_valid
   end
 
   describe '#name' do
@@ -43,4 +44,18 @@
     expect(Label.new(name: 'my label', color: '#ff00ff')).to be_valid
     expect(Label.new(name: 'my label', color: '#f0f')).to be_valid
   end
+
+  describe '#file_records' do
+    subject { build(:label, :with_files).file_records }
+
+    it { is_expected.to_not be_nil }
+    it { is_expected.to_not be_empty }
+
+    it 'does not have duplicate files associated to a single label' do
+      label = create(:label, :with_files)
+      label.file_records.build(labels: [label])
+
+      expect(label).to be_invalid # (same as to_not be_valid)
+    end
+  end
 end

From 6caf71ee5b693b44670da714e195afb8a574b8ff Mon Sep 17 00:00:00 2001
From: Sarah-Jeanne <>
Date: Tue, 12 Mar 2024 21:04:09 -0400
Subject: [PATCH 4/4] fixed lint-ruby

---
 .rubocop.yml                                    |  3 +++
 app/models/file_label.rb                        |  4 ++--
 app/models/file_record.rb                       |  3 ++-
 app/models/label.rb                             | 11 +++++++----
 db/migrate/20240308014217_create_labels.rb      |  2 ++
 db/migrate/20240311162807_create_file_labels.rb |  2 ++
 spec/factories/file_records.rb                  |  4 ++--
 spec/factories/labels.rb                        |  6 ++++--
 spec/models/label_spec.rb                       |  4 +++-
 9 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/.rubocop.yml b/.rubocop.yml
index 015de6ff..70fa522f 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -32,3 +32,6 @@ Rails/Output:
   Exclude:
     - 'lib/spooler.rb'
     - 'lib/spooler/**/*'
+
+Rails/UniqueValidationWithoutIndex:
+  Enabled: false
\ No newline at end of file
diff --git a/app/models/file_label.rb b/app/models/file_label.rb
index b1e7125b..208cb009 100644
--- a/app/models/file_label.rb
+++ b/app/models/file_label.rb
@@ -1,8 +1,8 @@
+# frozen_string_literal: true
+
 class FileLabel < ApplicationRecord
   belongs_to :file_record
   belongs_to :label
 
-  validates :file_record, presence: true
-  validates :label, presence: true
   validates :file_record_id, uniqueness: { scope: :label_id }
 end
diff --git a/app/models/file_record.rb b/app/models/file_record.rb
index 5567ef3e..db2021fe 100644
--- a/app/models/file_record.rb
+++ b/app/models/file_record.rb
@@ -3,7 +3,8 @@
 class FileRecord < ApplicationRecord
   has_one_attached :file
   has_many :jobs, class_name: 'Printer::Job', as: :executable, dependent: :nullify
-  has_many :file_labels
+
+  has_many :file_labels, dependent: :destroy
   has_many :labels, through: :file_labels
 
   validates :file, presence: true
diff --git a/app/models/label.rb b/app/models/label.rb
index 34832391..984b5f06 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
 class Label < ApplicationRecord
-    validates :name, presence: true, uniqueness: true
-    validates :color, presence: true, format: { with: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\Z/, message: "color should be an hex" }
+  validates :name, presence: true, uniqueness: true
+  validates :color, presence: true,
+                    format: { with: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\Z/ }
 
-    has_many :file_labels
-    has_many :file_records, through: :file_labels
+  has_many :file_labels, dependent: :destroy
+  has_many :file_records, through: :file_labels
 end
diff --git a/db/migrate/20240308014217_create_labels.rb b/db/migrate/20240308014217_create_labels.rb
index be1687ef..0885af07 100644
--- a/db/migrate/20240308014217_create_labels.rb
+++ b/db/migrate/20240308014217_create_labels.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class CreateLabels < ActiveRecord::Migration[7.1]
   def change
     create_table :labels do |t|
diff --git a/db/migrate/20240311162807_create_file_labels.rb b/db/migrate/20240311162807_create_file_labels.rb
index 9e2cdfdc..5347a6f1 100644
--- a/db/migrate/20240311162807_create_file_labels.rb
+++ b/db/migrate/20240311162807_create_file_labels.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class CreateFileLabels < ActiveRecord::Migration[7.1]
   def change
     create_table :file_labels do |t|
diff --git a/spec/factories/file_records.rb b/spec/factories/file_records.rb
index 4b519504..4a7e2fb0 100644
--- a/spec/factories/file_records.rb
+++ b/spec/factories/file_records.rb
@@ -20,8 +20,8 @@
       end
     end
 
-    trait(:with_labels) {
+    trait(:with_labels) do
       labels { build_list :label, 1 }
-    }
+    end
   end
 end
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index 9a1d8821..dc0c2120 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
 FactoryBot.define do
   factory :label do
     name { 'My red label' }
     color { '#ff0000' }
 
-    trait(:with_files) {
+    trait(:with_files) do
       file_records { build_list :file_record, 1 }
-    }
+    end
   end
 end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 70f01e5c..17361eb2 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe Label, type: :model do
@@ -27,7 +29,7 @@
   end
 
   it 'has a unique name' do
-    Label.create(name: 'My yellow label', color: '#0000ff') # same as create(:label, name: 'My yellow label', color: '#0000ff')
+    Label.create(name: 'My yellow label', color: '#0000ff')
     duplicate_label = Label.new(name: 'My yellow label', color: '#00f')
     expect(duplicate_label).to_not be_valid
   end