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
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ group :development, :test do

# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
gem "rubocop-rails-omakase", require: false

gem "rdoc"

gem "rails-controller-testing"
end

group :development do
Expand Down
6 changes: 6 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ GEM
activesupport (= 8.0.4)
bundler (>= 1.15.0)
railties (= 8.0.4)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
Expand Down Expand Up @@ -542,6 +546,8 @@ DEPENDENCIES
propshaft
puma (>= 5.0)
rails (~> 8.0.4)
rails-controller-testing
rdoc
rspec-rails (~> 8.0)
rubocop-rails-omakase
rubycritic
Expand Down
44 changes: 35 additions & 9 deletions app/models/modelo.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Classe que representa um modelo de questionário.
# Serve como agregador de perguntas e definições da avaliação.
class Modelo < ApplicationRecord
# Relacionamentos
has_many :perguntas, dependent: :destroy
Expand All @@ -15,37 +17,61 @@ class Modelo < ApplicationRecord
allow_destroy: true,
reject_if: :all_blank

# Método para verificar se modelo está em uso
# Verifica se o modelo já foi utilizado em alguma avaliação.
#
# Descrição: Checa se existem registros na tabela de avaliações associados a este modelo.
# Argumentos: Nenhum.
# Retorno: Boolean (true se estiver em uso, false caso contrário).
# Efeitos Colaterais: Nenhum (apenas leitura).
def em_uso?
avaliacoes.any?
end

# Método para clonar modelo com perguntas
# Cria uma cópia profunda do modelo e suas perguntas.
#
# Descrição: Duplica o objeto Modelo e itera sobre suas perguntas para duplicá-las também,
# associando-as ao novo modelo. O novo modelo nasce inativo.
# Argumentos:
# - novo_titulo (String): O título que será atribuído ao novo modelo clonado.
# Retorno: Objeto Modelo (a nova instância criada e salva).
# Efeitos Colaterais:
# - Cria um novo registro na tabela 'modelos'.
# - Cria novos registros na tabela 'perguntas'.
def clonar_com_perguntas(novo_titulo)
novo_modelo = dup
novo_modelo.titulo = novo_titulo
novo_modelo.ativo = false # Clones começam inativos
novo_modelo.save

if novo_modelo.persisted?
perguntas.each do |pergunta|
nova_pergunta = pergunta.dup
nova_pergunta.modelo = novo_modelo
nova_pergunta.save
end
# Copia as perguntas para a memória antes de salvar para passar na validação
perguntas.each do |pergunta|
nova_pergunta = pergunta.dup
novo_modelo.perguntas << nova_pergunta
end

novo_modelo.save
novo_modelo
end

private

# Valida se o modelo possui perguntas na criação.
#
# Descrição: Garante a regra de negócio de que um modelo não pode existir vazio.
# Argumentos: Nenhum.
# Retorno: Adiciona erro ao objeto se falhar.
# Efeitos Colaterais: Nenhum.
def deve_ter_pelo_menos_uma_pergunta
if perguntas.empty? || perguntas.all? { |p| p.marked_for_destruction? }
errors.add(:base, "Um modelo deve ter pelo menos uma pergunta")
end
end

# Valida se o usuário está tentando remover todas as perguntas na edição.
#
# Descrição: Impede que um update deixe o modelo órfão de perguntas.
# Argumentos: Nenhum.
# Retorno: Adiciona erro ao objeto se falhar.
# Efeitos Colaterais: Nenhum.
def nao_pode_remover_todas_perguntas
if persisted? && (perguntas.empty? || perguntas.all? { |p| p.marked_for_destruction? })
errors.add(:base, "Não é possível remover todas as perguntas de um modelo existente")
Expand Down
78 changes: 45 additions & 33 deletions app/models/pergunta.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Classe que representa uma pergunta associada a um modelo de questionário.
class Pergunta < ApplicationRecord
self.table_name = "perguntas" # Plural correto em português
self.table_name = "perguntas"

# Relacionamentos
belongs_to :modelo
Expand All @@ -21,61 +22,72 @@ class Pergunta < ApplicationRecord
validates :tipo, presence: true, inclusion: { in: TIPOS.keys }

# Validações condicionais
validate :opcoes_requeridas_para_multipla_escolha
validate :opcoes_requeridas_para_checkbox
validate :validar_minimo_opcoes, if: :requer_opcoes?

# Callbacks
before_validation :definir_ordem_padrao, on: :create

# Métodos
# Retorna o nome legível do tipo da pergunta.
#
# Descrição: Converte a chave do tipo (ex: 'texto_curto') para o valor legível (ex: 'Texto Curto').
# Argumentos: Nenhum.
# Retorno: String (O nome formatado do tipo).
# Efeitos Colaterais: Nenhum.
def tipo_humanizado
TIPOS[tipo] || tipo
end

# Verifica se o tipo de pergunta exige opções de resposta.
#
# Descrição: Checa se a pergunta é do tipo múltipla escolha ou checkbox.
# Argumentos: Nenhum.
# Retorno: Boolean.
# Efeitos Colaterais: Nenhum.
def requer_opcoes?
[ "multipla_escolha", "checkbox" ].include?(tipo)
%w[multipla_escolha checkbox].include?(tipo)
end

# Processa e retorna as opções da pergunta.
#
# Descrição: Normaliza o campo 'opcoes', lidando com Array, String JSON ou String separada por ponto e vírgula.
# Argumentos: Nenhum.
# Retorno: Array (Lista de strings com as opções).
# Efeitos Colaterais: Pode realizar parse de JSON.
def lista_opcoes
return [] unless opcoes.present?
# Assume que opcoes é JSON array ou string separada por ;

if opcoes.is_a?(Array)
opcoes
elsif opcoes.is_a?(String)
begin
JSON.parse(opcoes)
rescue JSON::ParserError
opcoes.split(";").map(&:strip)
end
parse_opcoes_string
else
[]
end
end

private

def definir_ordem_padrao
if modelo.present?
ultima_ordem = modelo.perguntas.maximum(:id) || 0
# Ordem pode ser baseada no ID para simplificar
end
# Auxiliar para converter string de opções em array.
#
# Descrição: Tenta parsear JSON, se falhar, usa split por ';'.
# Argumentos: Nenhum (usa atributo interno).
# Retorno: Array de strings.
# Efeitos Colaterais: Nenhum.
def parse_opcoes_string
JSON.parse(opcoes)
rescue JSON::ParserError
opcoes.split(";").map(&:strip)
end

def opcoes_requeridas_para_multipla_escolha
if tipo == "multipla_escolha"
opcoes_lista = lista_opcoes
if opcoes_lista.blank? || opcoes_lista.size < 2
errors.add(:opcoes, "deve ter pelo menos duas opções para múltipla escolha")
end
end
end
# Validação de quantidade mínima de opções.
#
# Descrição: Garante que perguntas de múltipla escolha tenham ao menos 2 alternativas.
# Argumentos: Nenhum.
# Retorno: Adiciona erro ao objeto se falhar.
# Efeitos Colaterais: Nenhum.
def validar_minimo_opcoes
opcoes_lista = lista_opcoes

def opcoes_requeridas_para_checkbox
if tipo == "checkbox"
opcoes_lista = lista_opcoes
if opcoes_lista.blank? || opcoes_lista.size < 2
errors.add(:opcoes, "deve ter pelo menos duas opções para checkbox")
end
if opcoes_lista.blank? || opcoes_lista.size < 2
nome_tipo = tipo == "multipla_escolha" ? "múltipla escolha" : "checkbox"
errors.add(:opcoes, "deve ter pelo menos duas opções para #{nome_tipo}")
end
end
end
17 changes: 17 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
# Classe que representa um usuário do sistema.
# Responsável pela autenticação, dados cadastrais e relação com turmas e submissões.
class User < ApplicationRecord
# Adiciona métodos para definir e autenticar senhas usando BCrypt.
#
# Descrição: Gera o atributo 'password_digest' e os atributos virtuais 'password' e 'password_confirmation'.
# Efeitos Colaterais: Criptografa a senha antes de salvar no banco.
has_secure_password

# Relacionamentos
has_many :sessions, dependent: :destroy
has_many :matricula_turmas
has_many :turmas, through: :matricula_turmas
# Associação com submissões onde o usuário atua como aluno
has_many :submissoes, class_name: "Submissao", foreign_key: :aluno_id, dependent: :destroy

# Validações de integridade dos dados
validates :email_address, presence: true, uniqueness: true
validates :login, presence: true, uniqueness: true
validates :matricula, presence: true, uniqueness: true
validates :nome, presence: true

# Normalização de atributos
#
# Descrição: Transforma o email e o login para letras minúsculas e remove espaços
# no início e fim antes de salvar ou consultar.
# Argumentos: Recebe o valor bruto do atributo (e/l).
# Retorno: String normalizada.
# Efeitos Colaterais: Altera o valor do atributo antes da validação.
normalizes :email_address, with: ->(e) { e.strip.downcase }
normalizes :login, with: ->(l) { l.strip.downcase }
end
3 changes: 3 additions & 0 deletions app/views/user_mailer/definicao_senha.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<h1>Definição de Senha</h1>
<p>Olá, <%= @user.nome %></p>
<p>Clique no link abaixo para definir sua senha.</p>
128 changes: 128 additions & 0 deletions doc/app/ApplicationRecord.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title>class ApplicationRecord - RDoc Documentation</title>


<meta name="keywords" content="ruby,class,ApplicationRecord">


<meta name="description" content="Documentation for the ApplicationRecord class">





<script type="text/javascript">
var rdoc_rel_prefix = "./";
var index_rel_prefix = "./";
</script>

<script src="./js/navigation.js" defer></script>
<script src="./js/search.js" defer></script>
<script src="./js/search_index.js" defer></script>
<script src="./js/searcher.js" defer></script>
<script src="./js/darkfish.js" defer></script>

<link href="./css/fonts.css" rel="stylesheet">
<link href="./css/rdoc.css" rel="stylesheet">



<body id="top" role="document" class="class">
<div id="navigation-toggle" role="button" tabindex="0" aria-label="Toggle sidebar" aria-expanded="true" aria-controls="navigation">
<span aria-hidden="true">&#9776;</span>
</div>


<nav id="navigation" role="navigation">
<div id="project-navigation">
<div id="home-section" role="region" title="Quick navigation" class="nav-section">
<h2>
<a href="./index.html" rel="home">Home</a>
</h2>

<div id="table-of-contents-navigation">
<a href="./table_of_contents.html#pages">Pages</a>
<a href="./table_of_contents.html#classes">Classes</a>
<a href="./table_of_contents.html#methods">Methods</a>
</div>
</div>

<div id="search-section" role="search" class="project-section initially-hidden">
<form action="#" method="get" accept-charset="utf-8">
<div id="search-field-wrapper">
<input id="search-field" role="combobox" aria-label="Search"
aria-autocomplete="list" aria-controls="search-results"
type="text" name="search" placeholder="Search (/) for a class, method, ..." spellcheck="false"
title="Type to search, Up and Down to navigate, Enter to load">
</div>

<ul id="search-results" aria-label="Search Results"
aria-busy="false" aria-expanded="false"
aria-atomic="false" class="initially-hidden"></ul>
</form>
</div>

</div>






<div id="parent-class-section" class="nav-section">
<h3>Ancestors</h3>
<ul><li>ActiveRecord::Base</li></ul>
</div>











<footer id="validator-badges" role="contentinfo">
<p><a href="https://validator.w3.org/check/referer">Validate</a></p>
<p>Generated by <a href="https://ruby.github.io/rdoc/">RDoc</a> 6.15.1.</p>
<p>Based on <a href="http://deveiate.org/projects/Darkfish-RDoc/">Darkfish</a> by <a href="http://deveiate.org">Michael Granger</a>.</p>
</footer>

</nav>

<main role="main" aria-labelledby="class-ApplicationRecord">



<h1 id="class-ApplicationRecord" class="anchor-link class">
class ApplicationRecord
</h1>

<section class="description">

</section>


<section id="5Buntitled-5D" class="documentation-section anchor-link">









</section>

</main>
</body>

Loading