Jako, że pierwszy raz piszę … witam wszystkich serdecznie.
Wprawdzie 1 przykazanie mówi “Nie stosuj podejścia <>” ale że już samo przerobienie “Ruby on Rails. Wprowadzenie” na railsach 2.1 było walką postanowiłem iść krok dalej - zaczynam pisać pierwszą aplikację. I już na samym początku trafiłem na problem. Stworzyłem tabelę zawierającą informacje o użytkownikach aplikacji, akcje, widoki … niby działa dodawanie użytkowników i modyfikowanie ich danych ale …
Chcę aby hasło było przechowywane w formie zaszyfrowanej. Zatem wyświetlanie zawartości pola password w widoku edycji nie ma sensu. Tylko, że modyfikacja danych bez ponownego wpisania hasła powoduje przesłanie pustego pola password do akcji update i tym samym wyczyszczenie hasła w tabeli.
Domyślam się, że należałoby podać jakiś warunek w pliku modelu, który rozpoznałby, że atrybut password ma zerową długość i nie dodawałby go do polecenia update ale jak to zrobić?
Obecna postać pliku modelu:
[code]require ‘digest/md5’
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
before_save :crypt_password
validates_confirmation_of :password, :message => “Hasło nie potwierdzone”
validates_uniqueness_of :name, :message => “Nazwa nie jest unikalna”
private
def self.md5(pass)
Digest::MD5.hexdigest(pass)
end
def crypt_password
write_attribute(“password”,self.class.md5(password))
end
end[/code]
Skorzystaj z pluginu restful_authentication, zajrzyj do środka żeby dowiedzieć się jak to jest zrobione.
Dłuższa odpowiedź:
Zmień nazwę pola w bazie na crypted_password i dodaj wirtualny atrybut:
attr_accessor| :password
Ten callback before_save powinień zrobić update pola encrypted_password tylko gdy to wirtualne pole nie jest puste, np:
def save_encrypted_password
update_attribute(:encrypted_password, Digest::MD5.hexdigest(password)) unless password.blank?
end
Oprócz tego musisz zabezpieczyć :crypted_password przed możliwością bezpośredniej zmiany z formularza (attr_accessible)
[code] validates_presence_of :password,
:if => :password_required?,
:message => “Hasło nie może być puste”
validates_presence_of :password_confirmation,
:if => :password_required? ,
:message => “Powtórzenie hasła nie może być puste”
…
def password_required?
!password.blank?
end[/code]
Najlepiej jak być użył pluginu restful authentication http://agilewebdevelopment.com/plugins/restful_authentication
Co do restful_authentication to na razie podaruję sobie z kilku powodów:
plugin ten dostępny jest wyłącznie w systemie kontroli wersji i nie ma nadanego numeru wersji - taki soft traktuję jako wersję rozwojową i raczej unikam (może w rails-ach panują inne zasady)
obecnie jestem na etapie nauki i wolę samodzielnie rozwiązywać problemy niż korzystać z gotowych rozwiązań, nie zależy mi na szybkim stworzeniu aplikacji ale na poznaniu ruby i railsów
Tak. Wszystkie pluginy zasysa się z repozytoriów, nie ma zipów do odpakowania.
Pozwól że zadam Ci intymne pytanie: programowałeś wcześniej w PHP, prawda?
W Railsach obowiązuje zasada DRY (don’t repeat yourself – jak już zrobiłeś jakąś niespecyficzną dla aplikacji funkcjonalność, najlepiej wydziel ją do plugina) oraz DRO (don’t repeat others – korzystaj z tego, co daje Ci społeczność).
Może do pluginów przekona Cię fakt, że część funkcjonalności RoR została w wersji 2.0 wywalona z “core” i wydzielona do pluginów.
Nie ma nic złego w pisaniu kodu od podstaw, szczególnie kiedy zaczynasz od podstaw. Dobrze jednak wiedzieć, że istnieją pluginy, z których możesz skorzystać, możesz też porównać swój kod z tym co jest w pluginie żeby się nauczyć się kilku (z reguły pożytecznych) rzeczy. Co do pluginów to tak jak napisał Tomash z reguły instaluje się je z svn’a/git’a dzięki czemu masz najnowszą stabilną wersję. To, że kod jest w systemie kontroli wersji nie oznacza, że jest to wersja rozwojowa (niestabilna). Restful_authentication to chyba najczęściej wykorzystywany plugin, znajdziesz go w użyciu na mnóstwie serwisów.
Nie mam nic przeciwko pluginom. Zresztą przerabiając “Ruby on Rails. Wprowadzenie” korzystałem z act_as_tree (wcześniej nieźle się tego naszukałem). W przyszłości z całą pewnością będę z nich korzystał ale dopiero jak nauczę się samodzielnie je robić.
W przypadku restful_authentication zadecydował jeszcze jeden argument. Pracuje na CentOs-ie i w dystrybucji nie ma pakietu git. Niestety “script/plugin install restful_authentication” skończył się komunikatem o braku polecenia git. Jak znajdę chwilkę czasu to zrobię odpowiedni pakiet (wyląduje w repozytorium Jazz Linux). W tej chwili mam ściągnięty ten plugin ze strony www i patrzę jak to jest zrobione.
[quote=hosiawak]Zmień nazwę pola w bazie na crypted_password i dodaj wirtualny atrybut:
attr_accessor| :password
Ten callback before_save powinień zrobić update pola encrypted_password tylko gdy to wirtualne pole nie jest puste, np:
def save_encrypted_password
update_attribute(:encrypted_password, Digest::MD5.hexdigest(password)) unless password.blank?
end
Oprócz tego musisz zabezpieczyć :crypted_password przed możliwością bezpośredniej zmiany z formularza (attr_accessible)[/quote]
Nieładnie tak “młodego” w maliny wpuszczać.
x.blank? nie znalazłem w dokumentacji API, wygląda na to, że to jakieś rozszerzenie, znalazłem natomiast empty?
jeśli crypted_password mam zabezpieczyć przed masową aktualizacją to IMHO attr_protected
A na koniec najlepsze. Obecnie kod modelu wygląda następująco:
[code]require ‘digest/md5’
class User < ActiveRecord::Base
attr_accessor :password
attr_protected :crypted_password
def self.md5(pass)
Digest::MD5.hexdigest(pass)
end
def save_crypted_password
update_attribute(:crypted_password,self.class.md5(password)) unless password.empty?
end
end[/code]
Niestety metoda save_crypted_password to jakaś nieskończona pętla i zupełnie nie rozumiem dlaczego. Wykonywane jest sprawdzenie warunku, potem aktualizacja pola :crypted_password i znowu sprawdzenie warunku, aktualizacja i tak w kółko.
def self.md5(pass)
Digest::MD5.hexdigest(pass)
end
def save_crypted_password
update_attribute(:crypted_password,self.class.md5(password)) unless password.empty?
end
end[/code]
Niestety metoda save_crypted_password to jakaś nieskończona pętla i zupełnie nie rozumiem dlaczego. Wykonywane jest sprawdzenie warunku, potem aktualizacja pola :crypted_password i znowu sprawdzenie warunku, aktualizacja i tak w kółko.[/quote]
Dzieje się tak dlatego, że szyrfujesz hasło wywołaniem w after_save.
update_attribute ustawia wartość i zapisuje rekord, co powoduje wywołanie callbacku after_save i tak w kółko, skutkując najpewniej błędem “Stack level too deep” (wartość password nie jest pusta, bo obiekt nie jest przeładowywany).
Zamiast after_save lepiej jest tutaj wrzucić szyfrowanie hasła do before_save bez wywołania update_attribute, po prostu przypisując konkretną wartość konkretnemu atrybutowi:
[quote=GhandaL]Dzieje się tak dlatego, że szyrfujesz hasło wywołaniem w after_save.
update_attribute ustawia wartość i zapisuje rekord, co powoduje wywołanie callbacku after_save i tak w kółko, skutkując najpewniej błędem “Stack level too deep” (wartość password nie jest pusta, bo obiekt nie jest przeładowywany).[/quote]
Tłumaczysz to bardzo logicznie ale problem musi leżeć gdzieś indziej. Początkowo miałem właśnie before_save i również miałem nieskończoną pętlę. Idąc podanym przez Ciebie tokiem rozumowania: update_attribute wykonywało aktualizację :crypted_password i aplikacja przechodziła do zapisania pozostałych pól ponownie wywołując before_save. Coś tu nie pasuje. Co spowodowało pierwsze wywołanie before_save? Wygląda na to, że update_attribute wogóle nie można stosować przy <…>_save.
Swoją drogą podane przez Ciebie rozwiązanie zadziałało idealnie. Dzięki.
GhandaL ma jak najbardziej rację. Callbacki after/before save są wywoływane (jak sama nazwa wskazuje) przed i po zapisie rekordu. Metoda update_attributes także zapisuje obiekt, a więc callbacki są wywoływane. Ty używasz prawd. save lub update_attributes, prawda?
Piszę taką wewnetrzną pocztę. Mam taką sytuację, że wyświetlają mi się tematy odebranych wiadomości w formie linku, klikając na link otwiera mi się treść tej wiadomości. Wszystko to działa na oddzielnych stronach, bez AJAXA. Chciałbym zrobić coś takiego, że jak otworzy mi się treść wiadomości to wtedy uzupełnic pole read_at w tabeli ‘messages’. Używam Sqlite3. Czy ktoś może robił coś takiego bądź mógłby pomóc? Jestem początkujący w Rails
Mozesz to zrobic na wiele sposobow np:
napisz sobie metode np
def readed(id)
readed = Message.find(id)
readed.read_at = Time.now
readed.save
end
no i w widoku gdy ktos wyswietla wiadomosc dajesz
<% readed(message.id) %>
i tyle
no ale rownie dobrze mozesz to dac do Controllera, do akcji show np, i tam jak ktos wyswietla wywolac jakas metode aktualizacji danych, albo pewnie na wiele innych jeszcze sposobow. No ale idee chyba nakreśliłem.