Sequel vs Active Record. Część 1 - słabości AR.

Opublikowane przez Jarosław Zabiełło Wed, 12 Dec 2007 23:37:00 GMT

Gdy framework Ruby on Rails (dalej: RoR lub Rails) rozpoczynał swoją błyskotliwą karierę, zachwycał wszystkich prostotą i elegancją konstrukcji. Jednym z fundamentalnych modułów Railsów jest Active Record, biblioteka realizująca symulację wirtualnej bazy obiektowej, pozwalającą na pracę z relacyjnymi bazami danych za pomocą obiektów języka Ruby. Active Record (dalej AR) nie uniknął jednak kilku potknięć projektowych.

Czytaj dalej...

Tagi , , ,  | 5 comments

Mały przegląd ORM'ów dla Rubiego

Opublikowane przez Jarosław Zabiełło Wed, 07 Nov 2007 23:16:00 GMT

Wcześniej pisałem o dostępnych bibliotekach implementujących warstwę Widoku z modelu MVC dla Ruby on Rails. Teraz krótko o ORM’ach. Jest ich już kilka.

Czytaj dalej...

Tagi , ,  | 17 comments

Elastyczność Railsów

Opublikowane przez Jarosław Zabiełło Wed, 04 Oct 2006 15:01:00 GMT

Większość osób, które zetknęły się z frameworkiem Ruby on Rails zauważyła, że domyślne ustawienia są tam tak dobrane, aby maksymalnie uprościć i zminimalizować ilość ręcznej pracy przy ustawieniach. Jeśli więc ktoś trzyma się flozofii i konwencji nazw (np. dla tabel i ich pól) będzie miał znacznie mniej dodatkowej roboty z konfiguracją frameworka do pracy. Typowa definicja modelu wyglądać może nawet tak prosto:

class User < ActiveRecord::Base
end

Powyższa definicja zakłada, że tabela w bazie nosi nazwę “users”, a jej primary key nazywa się “id”. W przeciwieństwie do innych ORM’ów, railsowy ActiveRecord nie wymaga żadnego wcześniejszego definiowania pól. Ruby bez problemu sam sobie wyciagnie wszystkie nazwy pól w sposób dynamiczny wtedy, kiedy będzie mu to potrzebne.

Nietypowe struktury

Niestety, nie zawsze jesteśmy w takiej komfortowej sytuacji, że mamy wpływ na nazewnictwo tabel i ich pól. Jednak to nie jest większym problemem, bo Rails sobie z tym radzi równie prosto.

class User < ActiveRecord::Base
  set_table_name 'uzytkownicy'
  set_primary_key 'IdUzytkownika'
end

Jak widać, dużo pracy nam nie przybyło. W tym wypadku musieliśmy tylko jawnie podać jak się nazywa nasza tabela i jak się nazywa jej klucz główny.

Korzystanie z wielu baz

W momencie tworzenia projektu aplikacji RoR generowany jest automatycznie plik konfiguracyjny zawierający połączenie do bazy danych. Przykładowy plik config/database.yml mogłby wyglądać np. tak:

default: &defaults
  adapter: mysql
  host: localhost
  encoding: utf8
  username: jakis_login
  password: jakies_haslo

development:
  database: bookstore_dev
  <<: *defaults

test:
  database: bookstore_test
  port: 3307
  <<: *defaults

production:
  database: bookstore
  socket: /var/run/mysqld/mysqld.sock
  <<: *defaults

Co robi powyższy kod? Definiuje 3 bazy: produkcyjną, roboczą i testową. (Ta ostatnia jest używana tylko do testów jednostkowych więc nie powinno się tam wstawiać żadnych istotnych danych) W w/w konfiguracji zdecydowaliśmy, że wszystkie bazy będą korzystać z MySQL >=4.1 z ustawionym wew. kodowaniem UTF-8 (zalecane). Baza produkcyjna działać będzie na Linuksie, więc zamiast portu TCP, podano uniksowy socket. Baza testowa działa na porcie 3307, a robocza na domyślnym (3306). Główna sekcja “defaults” jest wspólna dla wszystkich trzech baz zgodnie z zasadą DRY (Don’t Repeat Yourself).

Załóżmy jednak, że chcemy korzystać z dodatkowej bazy i chcemy aby z niej korzystały niektóre modele. Żaden problem. Do pliku dopisujemy tylko dodatkową definicję połączenia do innej bazy:

other:
  adapter: mysql
  host: 192.168.0.123
  database: customers
  <<: *defaults

Jak widać, możemy tą metodą tworzyć aplikację korzystającą z rozproszonych baz. (W tym wypadku baza customers leży na zupełnie innym serwerze, ale korzysta z tego samego usera i hasła.)

No dobrze, ale jak modele mają rozpoznać z jakiego połączenia korzystają? To też prosta sprawa. Domyślnym połączeniem jest to, co zdefiniowano w sekcji domyślnej. Jedynie modele, które korzystają z innego połączenia, muszą to mieć jawnie zadeklarowane w definicji, np.

class Client
  establish_connection :other
end

Model Client oraz wszystkie modele utworzone z niego na drodze dziedziczenia, będą korzystać z innego połączenia niż domyślne.

BTW, to jedna z bardziej brakujących mi cech we frameworku Django (Pylons nie ma z tym żadnego problemu). Mam nadzieję, że wkrótce to poprawią i w Django będzie można bez problemu korzystać z wielu baz.

Wspolne modele dla wielu aplikacji Rails

W tym scenariuszu mamy za zadanie stworzyć szereg aplikacji webowych korzystających z oddzielnych baz o tej samej strukturze tabel. Chcemy uniknąć powielania kodu niezbędnego dla modeli ORM. Nie mamy też czasu (ani ochoty) aby siedzieć i stworzyć definicje do wszystkich tabel wraz z wyposażeniem ich w nasze własne, dodatkowe metody uwzględniające każdą, możliwą potrzebę w przyszłości. Zamiast tego, wolimy tworzyć i rozbudowywać obiektowy opis naszej bazy dla wszystkich aplikacji w miarę ich rozwoju i wzrostu złożoności. Chcemy oby nasz model biznesowy, opisywany przez ActiveRecord, rósł stopniowo wraz ze wzrostem naszych aplikacji RoR.

Do takiego zadania, najlepiej aby wszystkie aplikacje RoR korzystały ze wspólnego repozytorium modeli. W wypadku systemów POSIX (Linux, czy MacOS-X) najprościej to rozwiązać za pomocą linków symbolicznych. Jednakże, jeśli chcemy aby nasz kod był bardziej przenośny i działał także pod Windowsami musimy inaczej podejść do tego problemu: wyniesiemy definicje naszych modeli do zewnętrznych modułów Rubiego aby domieszkowały one klasy poszczególnych modeli Rails.

Wpierw musimy dla każdej aplikacji RoR wskazać gdzie leży repozytorium z wpołdzielonymi modułami. Najlepiej wstawić do pliku conf/environment.rb zmienną globalną Rubiego o nazwie, np. $SHARED.

$SHARED = File.dirname(__FILE__)+'/../../shared/'

Konstrukcja File.dirname… jest zalecana, gdyż daje pewny i jednoznaczny dostęp do pliku bez względy na to gdzie przekopiujemy cały nasz projekt. (Identycznie należy robić pod PHP aby uniknąć problemów z ustawieniami zmiennej include_dirs. Większość pehapowego lamerstwa używa zapisu require(“plik.php”) i potem się dziwi że coś im nie działa)

Załóżmy że wcześniej mieliśmy model zdefiniowany następująco:

class Book
  set_table_name 'ksiazki'
  belongs_to :author
  def self.commented(user_id)
    self.find(:all, :conditions=>['user_id=?',user_id])
  end
end

Mamy tu więc nie tylko nazwę tabeli niezgodną z konwencją Railsów, ale także dodatkową metodę klasową. Wpierw zróbmy z tego moduł:

module BookModule
  def self.extended(c)
    c.set_table_name 'ksiazki'
    c.belongs_to :author
  end
  def commented(user_id)
    self.find(:all, :conditions=>['user_id=?',user_id])
  end
end

Kod korzystający z tego modułu przyjmie postać:

if RAILS_ENV == "development"
  load $SHARED + 'models/book.rb'
else
  require $SHARED + 'models/book.rb'
end

class Book< ActiveRecord::Base
  include BookModule
  extend BookModule
end

Górny fragment jest potrzebny jeśli chcemy aby Webrick/Mongrel w trybie development się przeładowywał podczas zmiany kodu w Ruby. “include” włącza metody instancji. Zaś “extend” włącza wszystkie emetody modułu _BookModule _do klasy jako jej metody klasowe. Zwróć uwagę, że w module nie definiowaliśmy ich jako z prefiksem self. Co prawda, efektem ubocznym tego przykładu jest to, że metoda commented() będzie możliwa do uruchomienia jako metoda zarówno instancji jak i klasy, ale to raczej nie ma żadnego znaczenia.

Posted in  | Tagi , ,  | 11 comments