czwartek, 28 kwietnia 2011

Prosta konfiguracja autocomplete w Ruby on Rails

Znowu walczyłem z autocomplete w Rails. Walczyłem, bo inaczej tego nie można określić.

Twórcą tego pluginu jest osobnik o swojsko brzmiącym nicku dhh. Na stronie poświęconej temu pluginowi można nawet (sic!) natknąć się na przykład jego wykorzystania. Przeglądając tę stronkę trudno coś o niej powiedzieć innego niż "łomatulu!". Wyjaśniać to ona niczego nie wyjaśnia, a tylko denerwuje niedziałającymi linkami scriptacoulosa.

Aby zainstalować z konsoli zawołaj:
ruby script/plugin install auto_complete
oczywiście zazwyczaj nie działa :-(
Można więc ręcznie. Ściągnij zatem plugin i rozpakuj do
/vendor/plugins/auto_complete
zazwyczaj pomaga.

Aby użyć tej kontrolki w widoku wpisz:
<%= text_field_with_auto_complete :model_name, 
                                  :field_in_model_to_search, 
                                  {:value => ""}, {:min_chars => 3, :after_update_element => 'approveForm'} %>
gdzie approveForm jest nazwą funkcji JavaScript, która zostanie wywołana po wybraniu elementu z listy.

Aby rozwikłać pozostałe zawiłości złapałem się po raz kolejny za google i gugłam. Czytam, czytam... i nadziwić się nie mogę. Zerknij tylko (albo raczej nie zerkaj) na railsforum, albo nawet na railscasts. To jakaś masakra jest!

Chodziło mi o prostą rzecz. Chciałem sam zapanować nad zapytaniem do bazy i nad tym co ona zwróci do użytkownika.
Najbardziej zdziwił mnie fakt, że to poniżej działa!!!
class CustomersController < ApplicationController
  auto_complete_for :customer, :name, :conditions => ["gender = ?", "female"]
  .
  .
  .
Zadziałało! Uradowany, nigdzie tego nie wyczytałem, tylko wykombinowałem "na macanta".  Fajno jest. I tak zostało na jakiś czas.
Gdy w następnym miejscu aplikacji znów musiałem czegoś podobnego użyć, miałem prawie gotowe rozwiązanie:
class ProductsController < ApplicationController
  # Nie stosuj tego, to nie działa!
  auto_complete_for :product, :name, :conditions => ["group_id = ?", @group_id]
  .
  .
  .
ale to nie działa. Zmienna egzemplarza pojawia się za wcześnie. Trick z tą zmienną @group_id odpowiadał mi, więc nie chciałem stosować trzymania się prawą ręką za lewe ucho i zacząłem drążyć temat. Po jakimś czasie okazało się, że notacja:
auto_complete_for :product, :name, :conditions => ["group_id = ?", @group_id]
tworzy dynamicznie metodę:
def auto_complete_for_product_name
  .
  .
  .
def
Co wiec stało na przeszkodzie aby taka metodę samemu napisać? Nic, więc napisałem:
def auto_complete_for_product_name
  product_name=params[:product][:name]
  conditions=["name = ? AND group_id = ?", product_name, @group_id]
  @products=Product.all(:conditions => conditions)
end
Sprawdzam... prawie działa.
Prawie bo Rails krzyczy, że brak mu pliku widoku auto_complete_for_product_name.erb
Znów "na macanta" poleciałem. Utworzyłem plik widoku i... zadziałało:
<ul> 
  <% @products.each do |product| %>
    <li><%= h product.name %></li> 
  <% end %> 
</ul> 
Rozwiązanie proste, łatwe i przyjemne.
W kontrolerze panuje się nad każdym przejawem tworzonej listy. Można zrobić dosłownie wszystko czego się tylko zapragnie. Nie trzeba z niczym kombinować jak to pokazywali w sreencaście i proponowali na liście. Moje rozwiązanie jest całkowicie naturalne i w duchu Rails.

Pozostała jeszcze sprawa pozostałych opcji.
Opcje są, a jakże. Żywcem je tu przytaczam:
# Required +options+ are:
# :url:: URL to call for autocompletion results
# in url_for format.
#
# Addtional +options+ are:
# :update:: Specifies the DOM ID of the element whose
# innerHTML should be updated with the autocomplete
# entries returned by the AJAX request.
# Defaults to field_id + '_auto_complete'
# :with:: A JavaScript expression specifying the
# parameters for the XMLHttpRequest. This defaults
# to 'fieldname=value'.
# :frequency:: Determines the time to wait after the last keystroke
# for the AJAX request to be initiated.
# :indicator:: Specifies the DOM ID of an element which will be
# displayed while autocomplete is running.
# :tokens:: A string or an array of strings containing
# separator tokens for tokenized incremental
# autocompletion. Example: :tokens => ',' would
# allow multiple autocompletion entries, separated
# by commas.
# :min_chars:: The minimum number of characters that should be
# in the input field before an Ajax call is made
# to the server.
# :on_hide:: A Javascript expression that is called when the
# autocompletion div is hidden. The expression
# should take two variables: element and update.
# Element is a DOM element for the field, update
# is a DOM element for the div from which the
# innerHTML is replaced.
# :on_show:: Like on_hide, only now the expression is called
# then the div is shown.
# :after_update_element:: A Javascript expression that is called when the
# user has selected one of the proposed values.
# The expression should take two variables: element and value.
# Element is a DOM element for the field, value
# is the value selected by the user.
# :select:: Pick the class of the element from which the value for
# insertion should be extracted. If this is not specified,
# the entire element is used.
# :method:: Specifies the HTTP verb to use when the autocompletion
# request is made. Defaults to POST.

Ogólna notacja kontrolki w widoku jest taka:
.
<%= text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {})  %>
.

Aby na przykład zacząć wyszukiwanie od wpisanych trzech liter kontrolkę skomponuj tak:
.
<%= text_field_with_auto_complete :employee, :name, {}, {:min_chars => 3} %>
.

wtorek, 26 kwietnia 2011

Screencasty o Vim'ie

Wybaczcie, że nie będę oryginalny ale dzięki blipowi niezauważenie zacząłem oglądać arcyciekawe (dla mnie oczywiście) screencasty o vim'ie.
Dostępne to to jest pod adresem: http://vimcasts.org i cieszy, że jest taka rzesza entuzjastów.
Nie będę ukrywał, że piszący te słowa z chęcią do tych entuzjastów się przyznaje :-).

piątek, 22 kwietnia 2011

count vs length vs size w Rails

W Ruby zarówno #length jak i #size to synonimy podawania wielkości tablicy bądź tablicy asocjacyjnej.
W Railsowym ActiveRecord wygląda na to, że są trzy drogi podawania wielkości asocjacji:
  1. firm.customers.count - wykonuje standardowe zapytanie SQL COUNT. Co ciekawe można podawać warunki podzbioru. Na przykład :conditions => {:customer_name = "Nowak"}. Jeśli nastąpiło wcześniejsze zapytanie SQL COUNT, wtedy #count pokaże zawartość cache, jeśli nie, wykona nowe zapytanie.
  2. firm.customers.length - zawsze ładuje całą asocjację do pamięci, potem ją zlicza i podaje wielkość. Ważne jest, że ta metoda nie wymusza zapytania, jeśli zawartość asocjacji była już pobierana.
  3. firm.customers.size - ta metoda działa w przybliżeniu jako kombinacja dwóch powyższych. Jeśli acocjacja była już pobierana podaje po prostu jej wielkość i tyle. Jeśli nie była pobierana, to wykonuje #count z punktu 1.
Piszę o tym, bo czasami można się naciąć jeśli oczekuje się bardzo szybkiej operacji, a trwa ona zdecydowanie dłużej niż spodziewana. No i to cachowanie...
Więc właśnie. Dwa słowa odnośnie cachowania. Rozważmy następujące przypadki:
Przypadek 1:
@firm = Firm.find(params[:id]) 
@amount_customers_in_firm = @firm.customers.size 
#tu nastąpi zapytanie do bazy

Przypadek 2:
@firm = Firm.find(params[:id])
@customers = @firm.customers 
@amount_customers_in_firm = @firm.customers.size 
#tu zwrócona zostanie ilość rekordów pobrana z cache

Przypadek 3:
@firm = Firm.find(params[:id])
@customers = @firm.customers 
. # tu
. # jakieś
. # gęste
. # operacje
. # na rekordach
@amount_customers_in_firm = @firm.customers(true).size 
#tu wymuszam ponowne zapytanie do bazy z pominięciem cache

Więcej na temat cachowania asocjacji w Rails guides.
Zaś ogólnie o cachowaniu zawodowy artukuł z thewebfellas.


środa, 13 kwietnia 2011

Zainicjowanie danymi zdalnego repozytorium w Git

Czasami, choć rzadko, musimy zainicjować danymi zdalne repozytorium. Jak zdążyłem się zorientować czytając artykuły o Git'cie, są one pisane dla raczej dla programisty, którego marzeniem jest podłączenie się do istniejącego projektu, ściągnięcie kodu i włożenie do niego własnych trzech groszy. To napisałem dla tych, którzy zakładają projekty.

niedziela, 10 kwietnia 2011

Krótkie screencasty o systemie kontroli wersji - Git

Okazało się, że coś takiego jest:
http://gitcasts.com/

Nawet miło i zrozumiale zrobione. Fajnym akcentem jest to, że na koniec każdego z filmików pokazują się inne w tym temacie. Przydałoby się to na portalu o railscats.


środa, 6 kwietnia 2011

Odcinanie ogonków, czyli zamiana polskich liter w Ruby on Rails

     Napotkałem na problem polegający na tym aby tekst zawierający polskie znaki zamienić na tekst bez polskich znaków. Czyli napis "Łabędź i żółw" ma stać się napisem "Labedz i zolw".
Troszkę poszperałem w sieci, troszkę pokombinowałem i powstał taki kod:
require 'iconv'

class String
  def to_ascii
    ascii = "acelnoszzACELNOSZZ"
    cep = "\271\346\352\263\361\363\234\277\237\245\306\312\243\321\323\214\257\217"
    s = Iconv.new("cp1250", "UTF-8").iconv(self)
    s.tr!(cep, ascii)
    return (s.length==0) ? self : s 
  end
end
     Lubię Ruby za to całkowicie bezkonfliktowe i niezwykle proste wzbogacanie standardowych klas.
Na pierwszy rzut oka pada pytanie: po co sprawdzać długość łańcucha s? Więc to zagadka. Dla wszystkich, którzy zgadną i nadeślą rozwiązanie - nagroda :-)

     Jeśli potrzebujesz takiego rozwiązania to wrzuć powyższy listing do pliku na przykład o nazwie str_utl.rb i umieść w Railsach w katalogu lib, a do pliku environment.rb wstaw require 'str_utl'. U mnie działa to tak:
>> "Łabędź i żółw".to_ascii
>> "Labedz i zolw"