HemoHeroes / hemoheroes-ruby

6 stars 4 forks source link

#5 Antônio poderá criar uma conta de doador - MVP 1 #5

Open cschallen opened 8 years ago

cschallen commented 8 years ago

emailCOMO: Antônio. QUERO: Ser um doador HemoHeroes. PARA: Encontrar o melhor local para doação de sangue.

Cenários:

Tarefas:

Detalhes técnicos:

.:Devem ser desenvolvidos dois protótipos:.

Protótipo 1°:

Protótipo 2°:

yrachid commented 8 years ago

Primeira parte do Review

app/assets/javascripts/services/validate_form_service.js:

Podemos ver que existem algumas responsabilidades que podem ser melhor distribuídas:

Como podemos refatorar este arquivo para que estas responsabilidades fiquem melhor distribuídas?


Sugestão de refatoração do validate_form_service.js

Enquanto estava dando uma olhada no código deste serviço, pensei e testei uma refatoração que seria um tanto quanto drástica, pois mudaria o código por completo, porém, separaria as responsabilidades um pouco mais. Ela fica aqui como uma sugestão, que pode tomar um tempo caso vocês queiram implementar algo nesse sentido. O que acham de pensarmos juntos em uma forma de refatorar esse serviço e suas responsabilidades?

Poderíamos ter um serviço apenas de validators.js. O que acham?

var validators = (function() {

  var _fail = function(message) {
    return { valid: false, message: message };
  }

  var _succeed = function() {
    return { valid: true };
  }

  var cpf = function cpf(field, isOptional) {
    // Lógica para validar o cpf aqui
  };

  var email = function email(field, isOptional) {
    // Lógica para validar o email aqui
  };

  var phone = function phone(field, isOptional) {
    // Lógica para validar o telefone aqui
  };

  var lengthGreaterThan = function greaterThan(size) {
    return function (field, isOptional) {
      return (field.value.length > size)
      ? _succeed()
      : _fail('O valor precisa ter mais que ' + size + ' caracteres');
    }
  }

  return {
    cpf: cpf,
    email: email,
    phone: phone,
    lengthGreaterThan: lengthGreaterThan
  }

})();

Este serviço recebe um campo do DOM e uma flag para quando este campo for opcional em um formulário. Ele retorna um booleano indicando se este campo é válido. Quando o campo não for válido, o validator enviará uma mensagem com o motivo pelo qual a validação falhou.

E como usamos esses validators?

Bem, primeiramente, precisamos configurá-los com algumas informações no donator_register.js e depois precisamos que estas informações sejam processadas pelo nosso novo form_validation_service.js:

validateFormService.setupValidators(
  {
    formSelector: '#new_user_blood_donator',
    inputList: [
      { selector: '.js-validateName', validators: [validators.lengthGreaterThan(2)], on: ['focusout'] },
      { selector: '.js-validateCPF',  validators: [validators.cpf], on: ['keyup']  },
      { selector: '.js-validateEmail', validators: [validators.email], on: ['focusout'] },
      { selector: '.js-validatePhone', validators: [validators.phone], on: ['focusout'] },
      { selector: '.js-validateTerms', validators: [validators.selected], on: ['click'] },
      { selector: '.js-validatePassword', validators: [validators.lengthGreaterThan(6)], on: ['focusout'] },
      { selector: '.js-validatePasswordConfirmation', validators: [validators.passwordConfirmation('.js-validatePassword')], on: ['focusout'] }
    ],
    submitButtonSelector: '.js-validateButtonDonator'
  });

Então neste novo donator_register.js, focamos em validar o formulário como um todo, passando para nosso serviço algumas informações sobre nosso form. Enviamos o seletor pelo qual nosso serviço pode encontrar nosso form, os campos deste form que vão ser validados.

É especialmente importante observar a parte do inputList, pois é ali que decidimos qual campo vai ser validado por qual validador em quais eventos.

Depois que configuramos estas metainformações sobre nosso form, podemos dar uma olhada no que o nosso service vai fazer com elas:

var validateFormService = (function() {

  var formStatus = {};

  var _executeValidators = function executeValidators(input, inputField) {
    input.validators.forEach(function(validator) {
      formStatus[input.selector] = validator(inputField, input.optional);
    });
  };

  var _updateButton = function(buttonSelector, shouldEnableButton) {
    (shouldEnableButton)
    ? document.querySelector(buttonSelector).classList.remove('is-disabled')
    : document.querySelector(buttonSelector).classList.add('is-disabled');
  };

  var _updateForm = function(form, submitButtonSelector) {
    var isFormValid = true;

    for (var field in formStatus) {
      if (formStatus.hasOwnProperty(field)) {
        isFormValid = isFormValid && formStatus[field].valid;
      }
    }
     _updateButton(submitButtonSelector, isFormValid);
  };

  var setupValidators = function(formData) {
    var form = document.querySelector(formData.formSelector);

    formData.inputList.forEach(function(input) {
      var inputField = form.querySelector(input.selector);
      input.on.forEach(function(event) {
        inputField.addEventListener(event, function () {
          _executeValidators(input, inputField);
          _updateForm(form, formData.submitButtonSelector);
        });
      });
    });
  };

  return { setupValidators: setupValidators };

})();

Nosso service agora serve para processar as informações configuradas para nosso form, executando todos os validators que foram especificados para cada campo. Ele também sabe como gerenciar o estado do nosso form, liberando e bloqueando o botão de submit sempre que isso for necessário, baseado no estado de cada um dos campos (como está sendo feito hoje, utilizando arrays).

Outra coisa que não está sendo feita, mas que talvez pudesse ser feita pelo service (ou delegada à outro componente, o que seria mais interessante) é a exibição das mensagens de erro em cada um dos campos. Lembrem-se que nosso service agora tem acesso à estas mensagens que são retornadas pelos nossos validators, bastaria delegarmos a exibição desta lista à alguém.

Máscaras

Outra responsabilidade do nosso serviço era o de mascarar campos com o VMask em certos eventos. Podemos desacoplar essas tarefas, certo? Como?

Novamente, no nosso, donator_register.js, podemos fazer algumas configurações para isso:

maskService.setupMasks({
  formSelector: '#new_user_blood_donator',
  inputList: [
    { selector: '.js-validateCPF', mask: masks.cpf, on: 'keyup' },
    { selector: '.js-validatePhone', mask: masks.phone, on: 'keyup' },
    { selector: '.js-validateCNPJ', mask: masks.cnpj, on: 'keyup' }
  ],
  submitButtonSelector: '.js-validateButtonDonator'
});

Indicamos à um serviço de máscaras(maskService.setupMasks), quem(selector) no nosso formulário(formSelector) vai ser mascarado com o quê(mask) e quando(on).

Para cada máscara, podemos ter um objeto com duas funções: applyMask que aplica a máscara ao campo e removeMask, que remove a máscara aplicada:

var PHONE_FORMAT = "(99)9999-9999";
var CPF_FORMAT = "999.999.999-99";
var CNPJ_FORMAT = "99.999.999/9999-99";
var CPF_REMOVAL_REGEX = /([.-])/g;
var CNPJ_REMOVAL_REGEX = /([.-])/g;
var PHONE_REMOVAL_REGEX = /([\(\)-])/g;

var masks = (function() {

  var simpleMask = function(pattern, removalRegex) {
    return {
      applyMask: function (field) {
        VMasker(field).maskPattern(pattern);
      },
      removeMask: function (field) {
        field.value = field.value.replace(removalRegex, "");
      }
    }
  }

  return {
    cpf: simpleMask(CPF_FORMAT, CPF_REMOVAL_REGEX),
    cnpj: simpleMask(CNPJ_FORMAT, CNPJ_REMOVAL_REGEX),
    phone: simpleMask(PHONE_FORMAT, PHONE_REMOVAL_REGEX)
  };

})();

E, por fim, temos o maskService para aplicar as máscaras definidas no donator_register.js:

var maskService = (function() {

  var setupMasks = function(maskConfig) {

    var form = document.querySelector(maskConfig.formSelector);

    maskConfig.inputList.forEach(function(input) {
      var inputField = form.querySelector(input.selector);
      inputField.addEventListener(input.on, function() {
        input.mask.applyMask(inputField);
      });
    });

  };

  return { setupMasks: setupMasks };
})();

Lembrem-se

Esta sugestão é apenas uma sugestão. Não é a resposta absoulta para o refactoring de vocês. Estes arquivos precisam ser refatorados, mas não precisa ser exatamente deste jeito.

O ideal é que vocês tenham claro em mente a necessidade de ajustarmos estes arquivos para que eles tenham responsabilidades menores e mais claras, independente de como vamos fazer isso.

Podemos conversar mais à respeito e encontrar uma solução que não tome tanto o tempo de vocês.

yrachid commented 8 years ago

Porquê o campo gênero possui apenas duas opções?

yrachid commented 8 years ago

Segunda parte do Review

app/controllers/user_blood_donators_controller.rb app/controllers/user_blood_banks_controller.rb:

app/controllers/user_blood_donators/passwords_controller.rb: app/controllers/user_blood_donators/confirmations_controller.rb: app/controllers/user_blood_donators/omniauth_callbacks_controller.rb: app/controllers/user_blood_donators/unlocks_controller.rb:

app/controllers/user_blood_donators/registrations_controller.rb: app/controllers/user_blood_donators/sessions_controller.rb:

controllers/user_blood_donators/registrations#create:

controllers/user_blood_donators/registrations#send_notification:

Dúvidas

app/controllers/user_blood_donators/registrations_controller.rb:

controllers/user_blood_donators/registrations#teste_chamada:

controllers/user_blood_donators/registrations#get_donator:

Era isso pessoal

Se precisarem de ajuda, me chamem no Slack