diff --git a/Gemfile b/Gemfile
index 1a1aade..80a6d8e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -41,7 +41,7 @@ gem "tzinfo-data", platforms: [:windows, :jruby]
gem "bootsnap", require: false
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
-# gem "image_processing", "~> 1.2"
+gem "image_processing", "~> 1.2"
group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
diff --git a/Gemfile.lock b/Gemfile.lock
index 732ee28..e600305 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -124,12 +124,21 @@ GEM
railties (>= 5.0.0)
faker (3.4.2)
i18n (>= 1.8.11, < 2)
+ ffi (1.17.0-aarch64-linux-gnu)
+ ffi (1.17.0-arm-linux-gnu)
+ ffi (1.17.0-arm64-darwin)
+ ffi (1.17.0-x86-linux-gnu)
+ ffi (1.17.0-x86_64-darwin)
+ ffi (1.17.0-x86_64-linux-gnu)
friendly_id (5.5.1)
activerecord (>= 4.0.0)
globalid (1.2.1)
activesupport (>= 6.1)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
+ image_processing (1.13.0)
+ mini_magick (>= 4.9.5, < 5)
+ ruby-vips (>= 2.0.17, < 3)
inertia_rails (3.2.0)
railties (>= 5)
io-console (0.7.2)
@@ -155,6 +164,7 @@ GEM
net-smtp
marcel (1.0.4)
matrix (0.4.2)
+ mini_magick (4.13.2)
mini_mime (1.1.5)
minitest (5.25.1)
msgpack (1.7.2)
@@ -295,6 +305,9 @@ GEM
ruby-lsp-rspec (0.1.12)
ruby-lsp (~> 0.17.0)
ruby-progressbar (1.13.0)
+ ruby-vips (2.2.2)
+ ffi (~> 1.12)
+ logger
rubyzip (2.3.2)
securerandom (0.3.1)
selenium-webdriver (4.24.0)
@@ -380,6 +393,7 @@ DEPENDENCIES
factory_bot_rails (~> 6.4)
faker (~> 3.3)
friendly_id (~> 5.5)
+ image_processing (~> 1.2)
inertia_rails (~> 3.1)
jbuilder
oj_serializers (~> 2.0)
diff --git a/app/controllers/companies_controller.rb b/app/controllers/companies_controller.rb
index 411a150..48ef4ca 100644
--- a/app/controllers/companies_controller.rb
+++ b/app/controllers/companies_controller.rb
@@ -18,6 +18,6 @@ def show
private
def company_scope
- Company.includes(:technologies, :continent, :country, :region, :city)
+ Company.includes(:technologies, :continent, :country, :region, :city, :logo_attachment)
end
end
diff --git a/app/frontend/pages/companies/show.tsx b/app/frontend/pages/companies/show.tsx
index 78c8d3f..42f6e65 100644
--- a/app/frontend/pages/companies/show.tsx
+++ b/app/frontend/pages/companies/show.tsx
@@ -25,6 +25,7 @@ function Show({ company }: Props) {
return (
+
{company.name}
{company.website && (
diff --git a/app/frontend/types/company.ts b/app/frontend/types/company.ts
index 15f2847..efdcbc8 100644
--- a/app/frontend/types/company.ts
+++ b/app/frontend/types/company.ts
@@ -14,6 +14,7 @@ export interface Company {
medium: string;
full: string;
};
+ logoUrl?: string;
continent: GeoFragment;
country: GeoFragment;
region: GeoFragment;
diff --git a/app/models/company.rb b/app/models/company.rb
index 7652c29..916c76c 100644
--- a/app/models/company.rb
+++ b/app/models/company.rb
@@ -14,6 +14,8 @@ class Company < ApplicationRecord
has_one :country, through: :region
has_one :continent, through: :country
+ has_one_attached :logo
+
validates :name, presence: true
validates :website, url: { no_local: true, public_suffix: true }
validates :careers_page, url: { no_local: true, public_suffix: true }, allow_blank: true
diff --git a/app/serializers/company_serializer.rb b/app/serializers/company_serializer.rb
index d23dd41..7d244a0 100644
--- a/app/serializers/company_serializer.rb
+++ b/app/serializers/company_serializer.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class CompanySerializer < ApplicationSerializer
+ include Rails.application.routes.url_helpers
+
attributes(
:id,
:slug,
@@ -18,6 +20,12 @@ class CompanySerializer < ApplicationSerializer
}
end
+ attribute :logo_url do
+ return unless company&.logo&.attached?
+
+ rails_representation_url(company.logo.variant(resize_to_fit: [100, 100]).processed, only_path: true)
+ end
+
has_many :technologies, serializer: TechnologySerializer
has_one :continent, serializer: GeoFragmentSerializer
has_one :country, serializer: GeoFragmentSerializer
diff --git a/db/migrate/20240908134818_create_active_storage_tables.active_storage.rb b/db/migrate/20240908134818_create_active_storage_tables.active_storage.rb
new file mode 100644
index 0000000..9be0353
--- /dev/null
+++ b/db/migrate/20240908134818_create_active_storage_tables.active_storage.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+# This migration comes from active_storage (originally 20170806125915)
+class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
+ def change
+ # Use Active Record's configured type for primary and foreign keys
+ primary_key_type, foreign_key_type = primary_and_foreign_key_types
+
+ create_table(:active_storage_blobs, id: primary_key_type) do |t|
+ t.string(:key, null: false)
+ t.string(:filename, null: false)
+ t.string(:content_type)
+ t.text(:metadata)
+ t.string(:service_name, null: false)
+ t.bigint(:byte_size, null: false)
+ t.string(:checksum)
+
+ if connection.supports_datetime_with_precision?
+ t.datetime(:created_at, precision: 6, null: false)
+ else
+ t.datetime(:created_at, null: false)
+ end
+
+ t.index([:key], unique: true)
+ end
+
+ create_table(:active_storage_attachments, id: primary_key_type) do |t|
+ t.string(:name, null: false)
+ t.references(:record, null: false, polymorphic: true, index: false, type: foreign_key_type)
+ t.references(:blob, null: false, type: foreign_key_type)
+
+ if connection.supports_datetime_with_precision?
+ t.datetime(:created_at, precision: 6, null: false)
+ else
+ t.datetime(:created_at, null: false)
+ end
+
+ t.index(
+ [:record_type, :record_id, :name, :blob_id],
+ name: :index_active_storage_attachments_uniqueness,
+ unique: true,
+ )
+ t.foreign_key(:active_storage_blobs, column: :blob_id)
+ end
+
+ create_table(:active_storage_variant_records, id: primary_key_type) do |t|
+ t.belongs_to(:blob, null: false, index: false, type: foreign_key_type)
+ t.string(:variation_digest, null: false)
+
+ t.index([:blob_id, :variation_digest], name: :index_active_storage_variant_records_uniqueness, unique: true)
+ t.foreign_key(:active_storage_blobs, column: :blob_id)
+ end
+ end
+
+ private
+
+ def primary_and_foreign_key_types
+ config = Rails.configuration.generators
+ setting = config.options[config.orm][:primary_key_type]
+ primary_key_type = setting || :primary_key
+ foreign_key_type = setting || :bigint
+ [primary_key_type, foreign_key_type]
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7c381cc..f7f664e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -14,6 +14,34 @@
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
+ create_table "active_storage_attachments", force: :cascade do |t|
+ t.string "name", null: false
+ t.string "record_type", null: false
+ t.bigint "record_id", null: false
+ t.bigint "blob_id", null: false
+ t.datetime "created_at", null: false
+ t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
+ t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
+ end
+
+ create_table "active_storage_blobs", force: :cascade do |t|
+ t.string "key", null: false
+ t.string "filename", null: false
+ t.string "content_type"
+ t.text "metadata"
+ t.string "service_name", null: false
+ t.bigint "byte_size", null: false
+ t.string "checksum"
+ t.datetime "created_at", null: false
+ t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
+ end
+
+ create_table "active_storage_variant_records", force: :cascade do |t|
+ t.bigint "blob_id", null: false
+ t.string "variation_digest", null: false
+ t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
+ end
+
create_table "cities", force: :cascade do |t|
t.string "name", null: false
t.bigint "region_id", null: false
@@ -93,6 +121,8 @@
t.index ["name"], name: "index_technologies_on_name", unique: true
end
+ add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
+ add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "cities", "regions"
add_foreign_key "companies", "cities"
add_foreign_key "company_technologies", "companies"
diff --git a/db/seeds.rb b/db/seeds.rb
index 2aa06fc..16b1128 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -85,6 +85,11 @@ def seed_companies
and Internal Tool Builder for Ruby on Rails that saves engineers and teams months of development time.",
city: @bucharest,
technologies: [@ruby, @rails],
+ logo: {
+ io: File.open(Rails.root.join("db/seeds/avohq.png")),
+ filename: "avohq.png",
+ content_type: "image/png",
+ },
)
create_records(Company, 48) do |i|
diff --git a/db/seeds/avohq.png b/db/seeds/avohq.png
new file mode 100644
index 0000000..db073c2
Binary files /dev/null and b/db/seeds/avohq.png differ
diff --git a/spec/controllers/companies_controller_spec.rb b/spec/controllers/companies_controller_spec.rb
index abb82eb..b624be0 100644
--- a/spec/controllers/companies_controller_spec.rb
+++ b/spec/controllers/companies_controller_spec.rb
@@ -6,6 +6,8 @@
require_factories
require_inertia
+ let(:company) { create(:company) }
+
describe "#index", inertia: true do
it "renders a list of companies" do
companies = create_list(:company, 5)
@@ -19,8 +21,6 @@
describe "#show", inertia: true do
it "renders a company" do
- company = create(:company)
-
get "/companies/#{company.slug}"
expect_inertia.to(render_component("companies/show"))
diff --git a/spec/serializers/company_serializer_spec.rb b/spec/serializers/company_serializer_spec.rb
new file mode 100644
index 0000000..e1b9fa4
--- /dev/null
+++ b/spec/serializers/company_serializer_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+RSpec.describe(CompanySerializer, type: :serializer) do
+ require_factories
+
+ let(:company) { create(:company) }
+ subject { CompanySerializer.one(company) }
+
+ describe "#logo_url" do
+ context "when logo is attached" do
+ let(:logo_url) { "/path/to/logo.png" }
+
+ before do
+ allow(company).to(receive_message_chain(:logo, :attached?).and_return(true))
+ allow(company).to(receive_message_chain(:logo, :variant, :processed).and_return(
+ instance_double(ActiveStorage::VariantWithRecord),
+ ))
+ allow_any_instance_of(CompanySerializer).to(receive(:rails_representation_url).and_return(logo_url))
+ end
+
+ it "returns the correct logo URL" do
+ expect(subject[:logoUrl]).to(eq(logo_url))
+ end
+ end
+
+ context "when logo is not attached" do
+ before do
+ allow(company).to(receive_message_chain(:logo, :attached?).and_return(false))
+ end
+
+ it "returns nil" do
+ expect(subject[:logoUrl]).to(be_nil)
+ end
+ end
+ end
+end