HemoHeroes / hemoheroes-ruby

6 stars 4 forks source link

#3 Kátia poderá solicitar doador - MVP 1 #3

Open cschallen opened 8 years ago

cschallen commented 8 years ago

COMO: Kátia QUERO: Solicitar os doadores de determinados tipo sanguíneo. PARA: Conseguir mais doações para completar o estoque de sangue.

Cenários:

Tarefas:

Notas:

Detalhes técnicos:

yrachid commented 8 years ago

Primeira Parte do Code Review

Vou escrevendo o code review aos poucos, para não atrasar vocês por muito tempo. O que eu for encontrando, vou colocando aqui.

0 app/assets/stylesheets/components/Button.scss 73:

1 app/assets/javascripts/utilities/only-in-view.js:

2 app/assets/javascripts/hospital_necessities.js 1:

3 app/assets/javascripts/hospital_necessities.js 17, 21:

4 app/assets/javascripts/hospital_necessities.js 20, 24:

Sugestões:

Refatorando o hospital_necessities.js

Aproveitem os first class functions do Javascript:

Ao invés de:

    var allInputNumbers = document.getElementsByClassName('js-necessityInput');

    for(var i = 0; i < allInputNumbers.length; i++){
      var inputNumber = allInputNumbers[i];
      inputNumber.addEventListener("keyup", function(){
        validateFormService.validatePositiveNumber();
        validateFormService.validateEmptyInput();
      })
      inputNumber.addEventListener("click", function(){
        validateFormService.validatePositiveNumber();
        validateFormService.validateEmptyInput();
      })
    }

Não poderia ser como abaixo?

    var allInputNumbers = document.getElementsByClassName('js-necessityInput');

    var validateOnAction = function() {
      validateFormService.validatePositiveNumber();
      validateFormService.validateEmptyInput();
    };

    //For each maneiro
    allInputNumbers.forEach(function(input) {
      input.addEventListener("keyup", validateOnAction);
      input.addEventListener("click", validateOnAction);
    });

Ou, ainda, poderia-se criar uma função que faça binding de múltiplos eventos à uma função de uma vez só (isso me parece interessante, pois vai ter bastante reuso desta função em outros scripts):

    var bindMultiple = function(component, events, action) {
      events.split(' ').forEach(function(event) {
        component.addEventListener(event, action);
      });
    };

    var validateOnAction = function() {
      validateFormService.validatePositiveNumber();
      validateFormService.validateEmptyInput();
    };

    allInputNumbers.forEach(function(input) {
      bindMultiple(input, "keyup click", validateOnAction);
    });

Ou

    var bindMultiple = function(component, events, action) {
      events.split(' ').forEach(function(event) {
        component.addEventListener(event, action);
      });
    };

    allInputNumbers.forEach(function(input) {
      bindMultiple(input, "keyup click", validateFormService.validatePositiveNumber);
      bindMultiple(input, "keyup click", validateFormService.validateEmptyInput);
    });
yrachid commented 8 years ago

Segunda parte do Review

Dúvida de negócio

Os campos do form de demanda podem ser 0?

Review técnico

Ponto 0 app/views/demand_blood_banks/_form.html.erb:

Ponto 1 app/assets/javascripts/hospital_necessities.js: 29:

Ponto 2 app/controllers/demand_blood_banks_controller.rb: 18:

Sugestões

Ponto 0

Um pouco mais sobre o template duplicado

Observando o template abaixo, podemos isolar duas coisas que não se repetem:

<div class="Form-groupDemand">
  <label class="Form-inputLabel u-desktop-size3of12  u-tablet-size3of12 u-mobile-3of12">A+</label>
  <%= f.number_field :a_positive, min: "0", max: "100", data: { type: "A+" },
  class: "Form-inputValue u-desktop-size4of12  u-tablet-size3of12 u-mobile-2of12 js-necessityInput js-confirmRequest"%>
  <span class="Form-inputDescription u-size4of12  u-tablet-size3of12 u-mobile-7of12 u-padding-0-0-0-10">doadores</span>
</div>

<div class="Form-groupDemand">
  <label class="Form-inputLabel u-desktop-size3of12  u-tablet-size3of12 u-mobile-3of12">B+</label>
  <%= f.number_field :b_positive, min: "0", max: "100", data: { type: "B+" },
  class: "Form-inputValue u-desktop-size4of12  u-tablet-size3of12 u-mobile-2of12 js-necessityInput js-confirmRequest" %>
  <span class="Form-inputDescription u-size4of12  u-tablet-size3of12 u-mobile-7of12 u-padding-0-0-0-10">doadores</span>
</div>

Os símbolos :a_positive e :b_positive e os data: { type: "A+"} e data: { type:"B+" }

Não é possível mandar uma lista de maps/hashes do controller para esta view nas actions de new e edit para processeramos esse pedaço do layout substituindo apenas estas duas informações dinamicamente?

Ponto 1

Nomes confusos

confirmRequest: Esta função possui muitas responsabilidades, talvez fosse interessante que ela até mesmo deixasse de existir. O que acham?

confirmRequestList: Me pareceu confuso, uma lista de confirmação? Do que? E se fosse requestedBloodTypes, requestedNeccessities ou algo assim?

inputsToConfirm e js-confirmRequest: E se fossem necessityInputs? Não ficaria mais claro que estamos falando de inputs de um form de necessities?

valuesOfConfirmInput:Me pareceu que eram múltiplos valores de um input chamado confirm.

Separando responsabilidades da função confirmRequest

Vamos analisar a função:

var confirmRequest = function(){
  var confirmRequestButton = document.querySelector('.js-nextButton');
  confirmRequestButton.addEventListener("click", function(){
    var inputsToConfirm = document.getElementsByClassName('js-confirmRequest');
    var valuesOfConfirmInput = {};

    for(var counter = 0, inputsToConfirmlength = inputsToConfirm.length; counter < inputsToConfirmlength; counter++) {
      var dataTypeAttribute = inputsToConfirm[counter].getAttribute('data-type');
      valuesOfConfirmInput[dataTypeAttribute] = inputsToConfirm[counter].value;
    }

    var confirmRequestList = document.querySelector('.js-confirmRequestList');

    Object.keys(valuesOfConfirmInput).forEach(function(key){

      if(valuesOfConfirmInput[key] != "") {
        var liTag = document.createElement("li");
        var requestText = document.createTextNode(valuesOfConfirmInput[key] + " do tipo " + key);

        confirmRequestList.appendChild(liTag);
        liTag.appendChild(requestText);

      }
    });
  });
};

Quantas responsabilidades ela possui:

  1. Ela adiciona um event listener ao botão de confirm
  2. Ela encontra todos os inputs de tipo sanguíneo
  3. Ela mapeia estes inputs para um objeto de tipo sanguineo e valor.
  4. Ela encontra no DOM, as lista de informativos de confirmação(confirm request list)
  5. Para cada input preenchido, ela adiciona à lista de informativos, um novo tipo sanguíno.
    1. Criando um elemento no DOM e adicionando à lista

Tema de casa (ou para um possível Dojo): Como resolver o problema da função confirmRequest possuir múltiplas responsabilidades? Podemos separá-las em funções menores e mais simples?

Metendo o loco e resolvendo o problema usando uma abordagem mais funcional

Javascript possui muitos aspectos e recursos de linguagens funcionais, e às vezes, estes aspectos nos ajudam bastante no dia a dia. Porém, há uma grande desvantagem nisso: a curva de aprendizado. Por conta disso, creio que esta segunda solução seja só para ter uma ideia de como poderíamos solucionar o problema usando as famosas funções map, filter, reduce, presentes em diversas linguagens. Recomendo estudarem esses conceitos, pois são extremamente úteis.

var _createInformationItemFromInput = function (input) {
  return document
  .createElement("li")
    .appendChild(
      document.createTextNode(input.value + " do tipo " + input['data-type'])
    );
};

var confirmRequest = function(){

  var confirmRequestList = document.querySelector('.js-confirmRequestList');

  document
  .querySelector('.js-nextButton')
  .addEventListener("click", function(){
    document
    .getElementsByClassName('js-confirmRequest')
    .filter(function(input) { return input.value !== ''})
    .map(_createInformationItemFromInput)
    .forEach(confirmRequestList.appendChild);
  });
};

Ponto 2

Corrigindo o problema do nil com operadores ternários:

Ao invés de:

@lastUpdateNecessity = DemandBloodBank.last.updated_at

Dá pra tentar:

@lastUpdateNecessity = DemandBloodBank.last ? DemandBloodBank.last.updated_at : Date.new # ou outra data
yrachid commented 8 years ago

Terceira e última parte do Review

Porque o arquivo app/assets/javascripts/hospital_necessities.js tem esse nome? Não podemos chamar de demand_blood_banks.js para ficar consistente com o domínio?

[0] app/assets/javascripts/hospital_necessities.js: 57,60 e 61:

Era isso!

Bom trabalho nesta estória pessoal! A única coisa que impede ela de ir adiante no board é o problema no Controller, que causa um bug sério à aplicação. Mas eu recomendaria fortemente que vocês refatorassem tudo. Lembrando também que as sugestões que deixei não são a única verdade absoluta do universo (afinal, são sugestões hehehe) e vocês podem refatorar o código da forma que acharem melhor.

Se precisarem de ajuda com alguma coisa, estarei à um Slack de distância, só chamar! o/