Skip to content
Open
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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ gem 'jbuilder', '~> 2.5'

# Use ActiveStorage variant
# gem 'mini_magick', '~> 4.8'
gem "mimemagic", "~> 0.3.10"
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Gemfile consistently uses single-quoted strings, but this new gem declaration uses double quotes. For consistency with the existing style in this file, switch this line to single quotes.

Suggested change
gem "mimemagic", "~> 0.3.10"
gem 'mimemagic', '~> 0.3.10'

Copilot uses AI. Check for mistakes.

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
Expand Down
5 changes: 4 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ GEM
marcel (0.3.2)
mimemagic (~> 0.3.2)
method_source (0.9.0)
mimemagic (0.3.2)
mimemagic (0.3.10)
nokogiri (~> 1)
rake
mini_mime (1.0.0)
mini_portile2 (2.3.0)
minitest (5.11.3)
Expand Down Expand Up @@ -199,6 +201,7 @@ DEPENDENCIES
coffee-rails (~> 4.2)
jbuilder (~> 2.5)
listen (>= 3.0.5, < 3.2)
mimemagic (~> 0.3.10)
puma (~> 3.12)
rails (~> 5.2.0)
sass-rails (~> 5.0)
Expand Down
226 changes: 223 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,225 @@
# Refactoring de relacionamento
# Ruby Dev Test 2 - Refactoring de Relacionamento

> A Madonna resolveu lançar um album em parceria com a Shakira! E agora?!
## Objetivo

Nosso PO jamais iria esperar que um album pudesse ter mais de um artista. Transforme a relacão 1 para N entre Player e Album em uma relação N para N. Precisamos de testes senão o chato do agilista vai brigar conosco!
Este projeto implementa o refactoring da relação entre `Player` e `Album`, alterando o relacionamento original de **1 para N** para **N para N**.

O cenário proposto considera que um álbum pode possuir mais de um artista, como no exemplo de uma colaboração entre Madonna e Shakira.

---

## Resumo da Mudança

Antes do refactoring, a modelagem era:

- `Player has_many :albums`
- `Album belongs_to :player`

Isso implicava a existência de uma coluna `player_id` na tabela `albums`, permitindo apenas um artista por álbum.

Após o refactoring, a modelagem passou a ser:

- `Player has_many :player_albums`
- `Player has_many :albums, through: :player_albums`
- `Album has_many :player_albums`
- `Album has_many :players, through: :player_albums`

Foi criada a tabela de junção `player_albums`, permitindo que um álbum esteja associado a múltiplos artistas.

---

## Principais Mudanças

### 1. Criação da tabela de junção

Foi criada a tabela `player_albums` para representar o relacionamento N para N entre artistas e álbuns.

Essa tabela contém:

- `player_id`
- `album_id`

Também foi adicionado um índice único para impedir duplicidade do mesmo relacionamento entre artista e álbum.

---

### 2. Migração dos dados existentes

Como o projeto original já possuía uma relação `albums.player_id`, foi criada uma migration de dados para preservar os vínculos existentes.

Essa migration copia os dados da relação antiga para a nova tabela `player_albums`.

### Por que usar migration para isso?

A migração de dados foi implementada em uma migration, e não em uma rake task, porque a transformação faz parte da evolução versionada do schema e precisa acontecer na ordem correta junto com as demais alterações estruturais.

Com isso, garantimos que:

- a criação da join table acontece antes do backfill
- os dados antigos são migrados antes da remoção da foreign key antiga
- qualquer ambiente que rode `db:migrate` chegue ao mesmo estado final

Uma rake task seria manual e não garantiria execução nem ordem correta.

---

### 3. Remoção da foreign key antiga

Após a migração dos dados, a coluna `player_id` foi removida da tabela `albums`.

---

### 4. Ajuste dos models

Os models foram atualizados para refletir o novo relacionamento N para N:

- `Player`
- `Album`
- `PlayerAlbum`

Também foi adicionada validação de unicidade na join table para impedir duplicidade do mesmo par `player` + `album`.

---

### 5. Atualização dos testes

Os testes foram adaptados para validar o novo comportamento:

- `Album` deixa de exigir `player`
- `Player` pode ter vários álbuns
- `Album` pode ter vários artistas
- `PlayerAlbum` garante presença e unicidade do relacionamento

Também foram ajustadas as fixtures para refletir o novo modelo.

---

## Decisões de Projeto

### Manter a stack original

O projeto foi mantido na stack original:

- Ruby 2.4.1
- Rails 5.2
- SQLite
- Minitest

A decisão foi preservar o escopo do exercício e evitar misturar o refactoring de domínio com um upgrade de stack.

### Dependência `mimemagic`

Foi necessário ajustar a dependência transitiva `mimemagic`, pois a versão originalmente referenciada no lockfile não está mais disponível no RubyGems.

A correção foi feita de forma mínima, apenas para restaurar a execução do projeto, sem promover upgrades amplos de dependências.

### Runtime JavaScript

Foi necessário ter um runtime JavaScript disponível no ambiente para o `uglifier` funcionar corretamente. A solução adotada foi instalar Node.js no ambiente local.

---

## Estrutura Final do Relacionamento

### Antes

```ruby
class Album < ApplicationRecord
belongs_to :player
end

class Player < ApplicationRecord
has_many :albums
end
```

### Depois

```ruby
class Album < ApplicationRecord
has_many :player_albums, dependent: :destroy
has_many :players, through: :player_albums
end

class Player < ApplicationRecord
has_many :player_albums, dependent: :destroy
has_many :albums, through: :player_albums
end
```

---

## Como rodar o projeto

## Pré-requisitos

- Ruby 2.4.1
- Bundler 1.16.1
- SQLite3
- Node.js

---

## Instalação

### 1. Clonar o repositório

```bash
git clone git@github.com:flaviolpgjr/ruby-dev-test-2.git
cd ruby-dev-test-2
```

### 2. Instalar dependências Ruby

```bash
bundle _1.16.1_ install
```

### 3. Preparar o banco

```bash
bundle _1.16.1_ exec rails db:create
bundle _1.16.1_ exec rails db:migrate
```

---

## Rodar os testes

```bash
bundle _1.16.1_ exec rails test
```

---

## O que os testes validam

### `AlbumTest`
- álbum válido com nome
- presença de nome
- relacionamento com múltiplos artistas

### `PlayerTest`
- player válido com nome
- presença de nome
- relacionamento com múltiplos álbuns

### `PlayerAlbumTest`
- relacionamento válido
- presença de `player`
- presença de `album`
- unicidade do par `player` + `album`

---

## Considerações Finais

A solução foi construída com foco em:

- preservação dos dados existentes
- simplicidade
- clareza do refactoring
- cobertura de testes
- respeito ao escopo do exercício

O refactoring foi implementado em etapas para reduzir risco e garantir consistência durante a transição do relacionamento 1 para N para um relacionamento N para N.
5 changes: 3 additions & 2 deletions app/models/album.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class Album < ApplicationRecord
belongs_to :player
has_many :player_albums, dependent: :destroy
has_many :players, through: :player_albums

validates_presence_of :name
end
end
5 changes: 3 additions & 2 deletions app/models/player.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class Player < ApplicationRecord
has_many :albums
has_many :player_albums, dependent: :destroy
has_many :albums, through: :player_albums

validates_presence_of :name
end
end
6 changes: 6 additions & 0 deletions app/models/player_album.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class PlayerAlbum < ApplicationRecord
belongs_to :player
belongs_to :album

validates :player_id, uniqueness: { scope: :album_id }
end
12 changes: 12 additions & 0 deletions db/migrate/20260414125603_create_player_albums.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class CreatePlayerAlbums < ActiveRecord::Migration[5.2]
def change
create_table :player_albums do |t|
t.references :player, foreign_key: true, null: false
t.references :album, foreign_key: true, null: false

t.timestamps
end

add_index :player_albums, [:player_id, :album_id], unique: true
end
end
16 changes: 16 additions & 0 deletions db/migrate/20260414125631_migrate_album_player_to_player_albums.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class MigrateAlbumPlayerToPlayerAlbums < ActiveRecord::Migration[5.2]
def up
Album.reset_column_information

Album.where.not(player_id: nil).find_each do |album|
PlayerAlbum.find_or_create_by!(
Comment on lines +2 to +6
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This data migration references the application models (Album/PlayerAlbum) directly. Migrations should avoid depending on app models because future refactors (renames, validations/callback changes, default scopes) can cause db:migrate to fail on fresh setups. Consider defining lightweight migration-local models (e.g., Class.new(ActiveRecord::Base) with self.table_name = ...) or using SQL/insert_all to backfill the join table.

Suggested change
def up
Album.reset_column_information
Album.where.not(player_id: nil).find_each do |album|
PlayerAlbum.find_or_create_by!(
class MigrationAlbum < ActiveRecord::Base
self.table_name = "albums"
end
class MigrationPlayerAlbum < ActiveRecord::Base
self.table_name = "player_albums"
end
def up
MigrationAlbum.reset_column_information
MigrationPlayerAlbum.reset_column_information
MigrationAlbum.where.not(player_id: nil).find_each do |album|
MigrationPlayerAlbum.find_or_create_by!(

Copilot uses AI. Check for mistakes.
player_id: album.player_id,
album_id: album.id
)
end
end

def down
raise ActiveRecord::IrreversibleMigration
end
end
5 changes: 5 additions & 0 deletions db/migrate/20260414131544_remove_player_id_from_albums.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class RemovePlayerIdFromAlbums < ActiveRecord::Migration[5.2]
def change
remove_reference :albums, :player, foreign_key: true
end
end
14 changes: 11 additions & 3 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,22 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2018_08_02_203647) do
ActiveRecord::Schema.define(version: 2026_04_14_131544) do

create_table "albums", force: :cascade do |t|
t.string "name"
t.integer "player_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["player_id"], name: "index_albums_on_player_id"
end

create_table "player_albums", force: :cascade do |t|
t.integer "player_id", null: false
t.integer "album_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["album_id"], name: "index_player_albums_on_album_id"
t.index ["player_id", "album_id"], name: "index_player_albums_on_player_id_and_album_id", unique: true
t.index ["player_id"], name: "index_player_albums_on_player_id"
end

create_table "players", force: :cascade do |t|
Expand Down
10 changes: 5 additions & 5 deletions test/fixtures/albums.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
fijacion:
name: Fijación Oral, Vol. 1
player: shakira

fixation:
oral_fixation:
name: Oral Fixation, Vol. 2
player: shakira

fixation:
she_wolf:
name: She Wolf
player: shakira

collab_album:
name: Madonna & Shakira
19 changes: 19 additions & 0 deletions test/fixtures/player_albums.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
shakira_fijacion:
player: shakira
album: fijacion

shakira_oral_fixation:
player: shakira
album: oral_fixation

shakira_she_wolf:
player: shakira
album: she_wolf

madonna_collab:
player: madonna
album: collab_album

shakira_collab:
player: shakira
album: collab_album
3 changes: 3 additions & 0 deletions test/fixtures/players.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ beyonce:

shakira:
name: Shakira

madonna:
name: Madonna
Loading
Loading