Dynamiczna zmiana katalogu public

Próbuję napisać aplikację działającą na zasadzie jedna aplikacja - wiele serwisów. Elementy charakterystyczne dla serwisu wybierane mają być w ApplicationController na podstawie nazwy domeny. Dynamiczne wybieranie bazy danych już opanowałem (jeszcze problem z migracjami ale jestem na dobrej drodze) pozostaje problem z dynamicznym ustawianiem katalogu “public”.
Dlaczego? W katalogu “public” znajduje się katalog “assets”, katalog z załącznikami (obsługa przez paperclip) oraz pliki utworzone przez caches_page. Wszystkie te elementy mają być różne dla różnych serwisów.

O ile odczytanie nazwy katalogu “public” nie jest problemem (Rails.public_path) to już ustawienie … Rails.public_path= kiedyś było ale od wersji 3.0.9 już nie istnieje. W jaki sposób zmienić w Rails 3.2.8 nazwę katalogu “public” tak by poprawnie działało cachowanie, pipe assets i paperclip? Czy sama zmiana Rails.application.paths[“public”]=“nowy_public” jest wystarczająca i czy to właściwe rozwiązanie?

Mam dziwne wrażenie że próbujesz pogłaskać kotka przy pomocy młotka. Jak widzisz na swojej drodze kamienie, i kamienie, to czemu iść tą drogą, czemu nie zrobić jak człowiek wiele instancji serwisów?

Może masz rację ale …

W firmie mamy aplikację działającą właśnie na tej zasadzie. Aplikacja została napisana ok. 10 lat temu w PHP 3 i była “poprawiana” przez wielu bardzo różnych programistów. W efekcie planowane jest napisanie jej od nowa. Aplikacja obsługuje obecnie ponad 200 serwisów.

Jeśli w kodzie znajdzie się jakiś błąd to poprawiam go w jednym miejscu i wszystkie serwisy działają poprawnie. Przy Twoim podejściu musiałbym poprawiać 200 serwisów.

Poza tym … skoro dało się w PHP to dlaczego nie w RoR? Chodzi głównie o poznanie RoR.

W tej chwili np. walczę z obsługą sesji w AR. Okazuje się, że session ustawiane jest jeszcze przed wykonaniem ApplicationController i mam problem: aplikacja nie znajduje sesji w tabeli sessions. Jeśli mam dwa kolejne zapytania do tego samego serwisu to wszystko jest OK bo aplikacja “pamięta” połączenie z bazą danych. Jeśli jednak nastąpi restart aplikacji lub zapytanie do innego serwisu (inna baza danych) to w trakcie ustawiania session aktywne jest odpowiednio połączenie ustawione na podstawie database.yml lub w poprzednim zapytaniu. Więc pojawia się pytanie: jak zmusić aplikację aby session było ustawiane po ustawieniu połączenia z odpowiednią bazą danych lub trzymać sesje w bazie danych określonej w database.yml ale gdzie znajdę definicję modelu Session żeby wymusić w nim połączenie do odpowiedniej bazy danych?

Istotnym jest również, że establish_connection zamyka wcześniejsze połączenie więc trzeba rozsądnie zaplanować aplikację aby zminimalizować ilość operacji zamykania i optwierania połączeń do różnych baz danych.

Możliwe, że próbuje “pogłaskać kotka przy pomocy młotka” ale rozwiązanie tej łamigłówki pozwali mi na lepsze poznanie RoR.

@Tuptuś: no właśnie chodzi mi o to po kiego się tak męczyć. Zapoznajesz się z chef albo puppet, deployujesz aplikację 200 razy i spokój.

Mam wrażenie że wiem co próbujesz osiągnąć ale idziesz zupełnie złą drogą, i niestety rails zupełnie się do tego typu aplikacji nie nadaje. Użyj innego frameworku (Sinatra, Padarino). Rails został napisany z zupełnie przeciwnymi zamierzaniami niż to co próbujesz osiągnąć. Nic więc dziwnego że na każdym kroku napotykasz problemy - po prostu masz okrągłą diurę a kwadratowy klocek. Jasne że z odpowiednio dużą ilością siły da się go wsadzić, ale po co?

Bo rozumiem że chodzi o to że baza danych jest taka sama (to znaczy ma taką samą strukturę), tylko dla każdego serwisu jest osobna?
I aplikacja działa tak że za każdym razem jak jest nawiązywane połłączenie łączy się z inną?

No więc na początek przenieś sesje do memcached - będzie łatwiej. Jeżeli chodzi o public to zależy jaki masz serwer www (nginx czy apache), ale generalnie passenger pozwala per host podać katalog “root” - zamiast jednego katalogu public ja bym zrobił po prostu po jednym katalogu dla każdego projektu - zapaskudzisz sobie drzewo projektu ale to chyba byłoby najprosstsze rozwiązanie.

Aha

Bo PHP to jezyk programowania a RoR to framework. Więc to porównywanie jabłek z pomarańczami.

W Ruby też się da coś takiego zrobić - w Ruby on Rails - zdecydowanie nie.

Widzisz Tuptus, to możesz być prekursorem nowego frameworka w ruby, który byłby przystosowany do tego typu zadań :).

Ja bym do tego podszedł zupełnie inaczej. Skoro jest na tym 200 stron, to traktowałbym to jako aplikację do hostowania stron, coś takiego jak http://get.harmonyapp.com. Tzn.: jedna baza, wiele kont użytkowników, użytkownicy mogący posiadać wiele stron.

Nie musicie tego koniecznie wypuszczać jako SaaS, jeżeli wasz model biznesowy wygląda inaczej, ale warto o tym pomyśleć.

@sarniak: nie kuś, własną dystrybucję linuksa już mam więc … :slight_smile: A tak na poważnie to brakuje mi wiedzy “od podstaw”. Nie jestem informatykiem z wykształcenia i te braki w podstawach zbyt często dają o sobie znać bym poważnie myślał o własnym framework-u.

@drogus: Pomysł jak najbardziej słuszny, rozważałem taką możliwość. Niestety ze względów formalnych niemożliwa jest realizacja tego pomysłu. Dane serwisów muszą być od siebie niezależne i klienci wyraźnie to zaznaczają w wymaganiach.

No to możesz próbować coś pohackować w railsach. Jeżeli rozwiążesz problem połączeń, to wydaje mi się, że nie będzie wielu innych problemów, nie demonizowałbym tego. Jeżeli railsy dostaną request, a w kontrolerze będziesz miał połączenie do “jakiejś” bazy, to reszta powinna pójść tak jakby ta baza zawsze była jedna. Będzie z tym oczywiście trochę problemów, np. trzeba uważać, żeby każda strona dostała oddzielną sesję, ale nie demonizowałbym tego (chociaż najlepiej przemyśl każdą część frameworka, od bazy danych, poprzez kontrolery, widoki, cookies itp. itd.)

Jeżeli chodzi o problem połączeń, to kwestia jest taka, że railsy po starcie tworzą threadpool, z którego później active record korzysta. Dlatego musiałbyś podmienić domyślną implementację zarządzania połączeniami, która tworzyłaby połączenia do bazy danych, która akurat jest potrzebna (warto też te połączenia trzymać w jakichś threadpoolach, żeby nie łączyć się przy każdym requeście, ale wtedy pewnie w mniejszych paczkach niż po 5, jezeli nie używasz wątków, to spokojnie wystarczy 1 połączenie).

W ostateczności możesz też otwierać połączenie do danej bazy tylko na czas requestu, musiałbyś tylko sprawdzić jaki jest tego koszt. Pewnie tak to jest rozwiązane obecnie (bo nie chce mi się wierzyć, że zrobili tam connection management z prawdziwego zdarzenia ;), ale może się zaskoczę).