Archive for category Agile

Czym Ajax jest, a czym nie?

ajax

ajax


Wpadłem ostatnio na dość ciekawą kwestię – błędne pojęcie na temat ajaxa skutkuje czasem błędną (bardziej skomplikowaną niż trzeba) implementacją jakiejś funkcjonalności.

Problem jest następujący: użytkownik wpisuje kod. My sprawdzamy go na serwerze i jeśli jest prawidłowy, to zwracamy pewne powiązane z nim właściwości (np. wysokość rabatu) i pewien komunikat, który musi być wyświetlony na stronie (np. w przypadku błędnego kodu “kod promocyjny jest niepoprawny”. Dodatkowe wymaganie jest takie: wysokość rabatu ma się pojawić koło ceny całkowitej, cena ma się zmniejszyć o ten rabat, a komunikat ma się pojawić nad przyciskiem “Zamów”, które jest w fizycznie w innym miejscu niż cena. Dobrze by było, gdyby w wypadku nieprawidłowego kodu przycisk zamów był nieaktywny.

Jeśli o Ajaxie myślisz tylko jak o sposobie na podmienianie fragmentów strony bez przeładowania to możesz skończyć z takim dziwadłem:

Wysyłasz trzy ajaxy – jeden update’uje jakiś element z rabatem, drugi pole z wiadomością, a trzeci (o zgrozo) przesyła kod html przycisku (raz disabled, raz enabled). No i jeszcze parsujesz, tniesz i sklejasz zwrócony rabat, żeby odjąć go od ceny.

Teraz zacznij myśleć o Ajaxie, który jest sposobem na komunikację z serwerem bez potrzeby przeładowania strony (przeładowanie strony to też komunikacja z serwerem – my pytamy, on odpowiada, o coś prosimy, serwer coś robi). Do tego po otrzymaniu odpowiedzi z serwera możemy z nią zrobić co chcemy – nie musi się automatycznie i bezmyślnie ładować do diva #ajaxResponseToParse ;) I w końcu, jeśli jeszcze nie wiesz, dowiedz się co to znaczy JSON.

Jeśli dodasz te trzy składniki to możesz skończyć z takim rozwiązaniem:

$.getJSON("http://example.com/check_code/XXX", function(data){
   $("#discount-ammount") = data.discount;
   $("#price").html(data.new_price); // *
   $("#message").html(data.message);
   $("#submit").get(0).disabled = data.submit_disabled;
});

(*) myślę, że dobrze takie rzeczy liczyć w jednym miejscu po stronie serwera.

Albo jednym słowem – wyjdź poza wygodne schematyczne myślenie i rozejrzyj się wokół. Możliwe, że wbijasz wkręt obcasem siedząc na młotku i gwoździe mając w kieszeni.

, ,

No Comments

Z każdym krokiem bliżej GIT a dalej SVN

Powoli przekonuję się go systemu zarządzania kodem źródłowym (scm) GIT. Pewnie zanim uda wprowadzić się go w firmie trochę minie czasu (przecież w zeszłym roku wdrożyliśmy SVN, które dał nam niezłego kopa przy pracy nad dużymi projektami).

Git jest mniej sławnym dzieckiem Linusa Torvaldsa (tak, tego samego od Linuksa), który zrozumiał, że SVN choć świetny – nie jest idealny.

Git-scm

Jeśli przeszukasz internet dowiesz się wiele o zaletach tego systemu wynikające z innej filozofii, która repozytorium nie traktuje jako zbioru kolejnych wersji, ale jako zbiór konkretnych zmian. Dzięki temu sytuacje, które w SVN wymagają rozwiązywania konfliktów w GIT najczęściej sprowadzają się do automatycznego scalania (merge) zmian. Podobno dzięki tej zmianie branchowanie z trudnego tricku zmienia się w wygodne i użyteczne narzędzie.

Sam postanowiłem delikatnie wejść w ten strumień. Są projekty, które robię samodzielnie. Nie wymagają one systemu zarządzania wersjami, skoro jestem jedyną osobą, która ma dostęp do kodu. Jednak jestem tak przyzwyczajony do udogodnień, które przynosi ze sobą scm, że i tak zakładam repozytorium.

W prywatnych projektach przesiadam się na GIT bo założenie repozytorium jest proste jak 1,2,3(4):

cd /sciezka/do/projektu
git init
git add .
git commit -m "zakładam repo ^_^"

, ,

1 Comment

Fat model, skinny controller – przykład

Niedawno przy projekcie trafiliśmy na ciekawy problem. Chciałbym się podzielić z Wami tym czego się nauczyliśmy.

Problem przedstawię w bardzo uproszczonej formie, bo trudno bez machania rękoma przy zabazgranej tablicy wyjaśnić go w całości.

Problem

Mamy formularz dodawania kosztów, który jest dość specyficzny. Można dodać “łysy koszt” i wtedy pojawia się formularz z wyborem firmy i innymi atrybutami. Można jednak z widoku konkretnej firmy wybrać opcję “dodaj koszt” – w takim wypadku lista wyboru firm się nie pojawia.

Dość naturalne jest załatwić to kontrolerem. Są różne możliwości, jedną z nich jest takie zdefiniowanie akcji:

function add($entryId=null) {
   if(!empty($this->data)){
       $this->Cost->save($this->data);
       // redirect w jakieś przyjemne okolice
   }
   if(is_null($entryId)){
      $this->set("entry", $this->Cost->Entry->read(null, $entryId);
   }else{
      $this->set("entries", $this->Cost->Entry->find("list");
   }
}

W widoku wiadomo – jeśli jest $entry, wyświetlimy $entry["Entry"]["name"] i w jakimś ukrytym inpucie wrzucimy id, jeśli istnieje $entries – wyświetlimy selecta.

Na razie w kontrolerze wygląda to wystarczająco zgrabnie, żeby tego nie ruszać. Ale jeśli znajdziesz się w sytuacji, gdy ilość przypadków jest coraz większa – możesz się zorientować, że dodajesz kolejne argumenty do metody add i rozbudowujesz tą metodę. W takiej sytuacji może Ci pomóc takie podejście.

Propozycja rozwiązania

Po pierwsze olej zwykłe parametry funkcji, a zacznij używać parametrów “named”. Czyli zamiast:
costs/add/1/3/45/23
costs/add/2///22
będziesz miał
costs/add/cost_id:1/entry_id:3/param3:45/param4:23
costs/add/cost_id:2/param4:22

Po drugie – przenieś logikę działania do modelu. np:

//model Cost
function giveMeProperData($params){
   if(isset($params["cost_id")){
      // do sth
      return $some_array;
   }
   if(isset($params["entry_id")){
      // do sth
      return $other_array;
   }
   return array();
}

Kontroler uprość do granic możliwości:

function add($entryId=null) {
   if(!empty($this->data)){
       $this->Cost->save($this->data);
       // redirect w jakieś przyjemne okolice
   }
   $this->data = $this->Cost->giveMeProperData($this->params["named"];
}

A w widoku w zależności od tego w jaki sposób wygląda $this->data renderuj widok.
Jeśli w tablicy jest “entry_id” to wyświetl zawartość $this->data["Entry"]["name"], a wartość $this->data["Entry"]["id"] przypisz do jakiegoś ukrytego pola. Itd.

Co zyskujesz?

  1. Kontroler robi to co do niego należy. Przestaje go interesować przypadek właśnie rozpatrywany. Jego interesuje tylko czy został przesłany formularz. Jeśli tak – spróbuj zapisać i przekierować, jeśli nie – pobierz dla niego dane i wyświetl (przekaż do widoku)
  2. Testowanie modeli, choć nie tak proste, jest o niebo łatwiejsze od testowania kontrolerów i widoków – możesz pokryć ważną część aplikacji testami. Możesz testować, czy metoda giveMeProperData() zwraca to, czego się spodziewasz dla konkretnych argumentów.
  3. Jeśli pokryjesz tą metodę testami będziesz mógł w komfortowych warunkach refaktoryzować ten kod – jak się pojawią powtórzenia wyodrębnisz wspólne części itd. Gdyby to siedziała w kontrolerze – nikt nie miałby ochoty tam zajrzeć, a testowanie polegało by głównie na odświeżaniu widoków w przeglądarce dla każdego z przypadków.
  4. Teraz widok jest klasą, która “ma wiedzę” na temat tego jak się zachować w zależności od otrzymanych danych (w prostych przypadkach to już się dzieje – chociażby w akcjach “edit”). Wcześniej część tej wiedzy znajdowała się w kontrolerze.
  5. Przyjemny side-effect wynikający z używania parametrów “named”: Teraz komponując link w widoku zamiast zastanawiać się czy formularz dodawania kosztów dla danej firmy jest pod /costs/add//[id_firmy]/ czy pod /costs/add///[id_firmy] – wiem, że jest pod /costs/add/entry_id:[id_firmy]

Jest oczywiście brak w tym rozwiązaniu – dość sporo kodu wędruje do widoku (który, jak wspomniałem, trudno testować). Jednak pozostawienie architektury w pierwotnym stanie tej kwestii nie rozwiązuje. Na początku masz problem z

  • dużą ilością kodu w kontrolerze
  • kodem kontrolera, który nie jest pokryty testami więc nie będzie refaktoryzowany
  • kodem w widoku, który nie jest pokryty testami

Po zastosowanej zmianie zostanie Ci tylko ostatni punkt. Samodzielnie możesz zdecydować, czy to się opłaca.

, ,

No Comments