Taki szalony pomysł dotyczący pluginów ;-)

Witam,

Gdy zobaczyłem w Radiancie extensions, czyli ichni sposób na proste rozszerzanie funkcjonalności CMSa, to stwierdziłem, że bardzo podoba mi się takie podejście. Jest tylko jeden minus. Żeby to zaimplementować w swojej aplikacji trzeba się trochę namęczyć i dodatkowo, żeby korzystać z dobrodziejstw rozszerzania widoków trzeba owe widoki odpowiednio zbudować.

Czyli na przykład chcę dodać kolejny link do menu głównego w panelu. Jeżeli jest odpowiednio zdefiniowany blok dla tego menu, to mogę w extension dodać:

admin.tabs.add "Link Roll", "/admin/link_roll", :after => "Layouts", :visibility => [:all]

Zastanawiałem się nad zrobieniem prostego systemu rozszerzeń do swojej aplikacji opartego na engine’ach (ewentualnie jak będę pisał w merbie to na slices), który dodatkowo miałby możliwość zmiany layoutu/widoków.

Z reguły jest tak, że jak piszę rozszerzenie do konkretnej aplikacji i dodatkowo jest to na przykład związane z dodaniem nowego kontrolera (na przykład dodaję obsługę galerii), to chciałbym po załadowaniu tego rozszerzenia dodać link “Galerie” w menu. Bez systemu takiego jak w radiancie trzeba to zrobić ręcznie.

Dlatego pomyślałem sobie, że najprościej by było użyć do tego nokogiri (wcześniej chciałem użyć hpricota, ale nokogiri jest szybszy i mniej zamotany :).

Czyli na przykład coś w stylu:

def activate with_layout "admin" do |layout| layout.css("#main_menu ul li").last do |last| last.add_next_sibling("<li><a href="/foo">bar</a></li>") end end end
Co o tym myślicie? Dobre? Głupie? Jakieś rażące braki/minusy w rozumowaniu? Ewentualnie może ktoś jest na tyle biegły w kodzie railsów (a może merba?), żeby stwierdził, czy łatwo będzie dodać coś takiego?

Ja bałem się o wydajność, ale jak pokazują benchmarki chyba nie ma się o co martwić. Wczytanie 500 razy strony slashdot.org zajmuje 8 sekund, parsowanie dużo mniej, szczególnie jeżeli używa się XPath. Coś jeszcze o czym powinienem pomyśleć?

Nie jestem pewien czy dobrze to rozumiem, chcesz modyfikować kod źródłowy aplikacji przez interfejs użytkownika?

Nie :slight_smile:

Chodzi o to, żeby wpiąć się z takim kodem w miejsce po wyrenderowaniu layoutu i zanim zostanie wysłany do użytkownika dodać coś od siebie.

Czyli na przykład jest to nieszczęsne main menu w panelu administracyjnym. Są tam pozycje typu: artykuły, pliki, aktualności itp. itd.

Dorzucam do tego rozszerzenie dodające obsługę galerii i jest mały problem. Niby wystarczy dodać do menu jednego linka, ale łatwiej byłoby to zrobić automatycznie w jakimś kodzie inicjującym plugin. Dlatego wrzucam kod z poprzedniego posta do tej metody inicjującej. Użyszkodnik odświeża stronę, renderuje się layout i zanim zostanie wysłany do przeglądarki to jest jeszcze parsowany przez nokogiri i aplikowane są te linijki z with_layout.

Jak tak teraz o tym zaczynam myśleć głębiej to wydaje się dość dziwne, ale dlatego właśnie wolę coś takiego napisać na forum, żeby nie stracić czasu na implementację bezsensownego rozwiązania :slight_smile:

i za każdym razem coś takiego ?? to nie prościej byłoby generować widoki np.z menu z bazy danych, po prostu dodając “galerię”, a przy takiej zmianie jakiś filtr mógłby resetować wygenerowany widok i wtedy generował by go jeszcze raz, tym razem z “galerią” ?? … nie wiem czy dobrze zrozumiałem ale jakoś tak :slight_smile: ??

Z tym parsowaniem to trochę lipa, zwłaszcza że za każdym razem. Co będzie jak dodasz drugi i trzeci kontroler? Najpierw pierwszy będzie parsował layout a potem kolejne.

Rozwiązanie z bazą jest dobre, tylko że dochodzi ci kolejny model, więc jak chcesz dodać parę linków to chyba za dużo zachodu.

Możesz jeszcze stworzyć moduł np. Extension :), który będzie trzymał globalną zmienną np. extension_links i jakąś metodę do dodawania nowych linków do tej zmiennej. I teraz w każdym kontrolerze, który jest rozszerzeniem aplikacji robisz coś takiego:

class GalleriesController < ... include Extension add_extension_link('/foo') end
coś w tym stylu, a potem w layoucie wyciągasz wszystkie linki ze zmiennej z tego moduły Extension i już.

Nie wiem czy to będzie działać i czy wogóle ma sens :slight_smile:

no ale w tym przypadku (kontroler ) chcąc dodać do layoutu nowy link trzeba dopisywać go w każdym kontrolerze z którego chcemy aby był widoczny ??

ogólnie to taki mechanizm w przypadku menu to trochę przegięcie ^^, chyba, że znajdzie się jakieś ambitniejsze zastosowanie to moze i warte to zachodu :slight_smile:

W założeniu to miało działać po wygenerowaniu layoutu, więc nie.

Menu to był tylko przykład. Tak samo kontroler :wink:

Jeżeli chodziłoby tylko o menu, to zrobiłbym pewnie podobne rozwiązanie do radiantowych extensions, tylko dużo prostsze.

Chodzi o to, że czasami trzeba dodać linka, czasami jakieś pole w edycji danego modelu, czasami dorzucić jakiś partial. Czasami coś usunąć.

Ale po odpowiednio długim czasie (od dzisiejszego ranka:P) i dogłębnym przemyśleniu stwierdzam, że to chyba jednak zbyt szalony pomysł ;] W razie czego zmerguję radiantowe extensions z lekkimi zmianami :slight_smile: Ale jak ktoś ma jakieś przemyślenia na ten temat, to niech pisze :wink:

nie, nie w każdym kontrolerze, tylko w tym który dodaje rozszerzenie

Generalnie cała idea polega na tym, żeby stworzyć jeden globalny obiekt, który przechowuje linki. I teraz jak dodajemy ten nowy kontroler to z niego dodajemy link do tego globalnego obiektu, a layout też ma dostęp do tego obiektu i może sobie wyciągnąć jakie linki trzeba dodać.

Tak sobie teraz pomyślałem, że wcale nie trzeba tego robić przez moduł i dołączanie go do kontrolera. Można w katalou initializer stworzyć po prostu tablicę, która będzie trzymała te linki, wtedy w kontrolerze dodajemy do niej linki, a layout sobie je potem wyciąga.

[code=ruby]#initializers/extensions.rb
EXTENTIONS_LINKS = []

#kontroler

to akurat brzydko, lepiej dodać jeszcze metodę która ukryje, że to tablica itp.

class GalleriesController
EXTENSION_LINKS << “/foo”
end

#widok
<% EXTENTIONS_LINKS.each do |link| %>

  • <%= link_to link %>
  • <% end %>[/code] Wydaje mi się, że to rozwiązanie jest bardzo proste, lżejsze od bazy no i na pewno miliard razy szybsze od tego parsowania za każdym razem widoku.

    Zgadzam się, aczkolwiek prosiłbym o zejście już z tych biednych linków. Tak jak napisałem powyżej, to był tylko przykład.

    Chodzi o coś bardziej elastycznego.

    Spójrzcie na przykład tutaj na bloki dołączone do metody render_region: http://github.com/radiant/radiant/tree/master/app/views/admin/page/edit.html.haml

    Do wszystkich tych regionów można się dostać za pomocą specjalnego API. Mi chodziło o to, żeby można zrobić coś podobnego, ale bez żadnych zmian w widokach.

    UPDATE: i tu właściwie można skończyć, bo tak jak napisałem - po przemyśleniu ten pomysł rzeczywiście jest zbyt szalony - w razie potrzeby lepiej zaimplementować extensions z radianta.

    Mimo, że temat zamknięty, to pozwolę sobie dorzucić trzy grosze.
    W każdym rozwiązaniu tego rodzaju musisz zapewnić pewien “hak”, który mogą wykorzystać rozszerzenia. W przykładzie z menu była nim obecność w layoucie węzła #main_menu, który dodatkowo zawierał listę. Niby niewiele, ale jeśli chce się coś takiego faktycznie wykorzystywać, to trzeba to jakoś ustandaryzować (standardowy węzeł dal menu, czegoś tam i czegoś tam…).

    W zasadzie chyba prościej jest zrobić rozwiązanie, w którym korzystamy ze standardowego, “wspólnego” layout, w którym są wywołania pewnych metod pomocniczych: include_menu, include_…, etc. a te metody np. “wstrzykują” wszystkie “zarajestrowane” pozycje menu, itd.
    Oczywiście layouty takie mogą być dowolnie modyfikowane, o ile zawierają wywołania tych metod pomocniczych.

    Mówiąc inaczej - w zasadzie nie ma możliwości dodawania rozszerzeń do aplikacji, jeśli nie zapewnimy jakiegoś minimalnego wsparcia po stronie aplikacji dla tych rozszerzeń. Czy będzie to odpowiedni węzeł w HTMLu, czy wywołanie metody w layoucie, to już w zasadzie kwestia gustu. Natomiast stworzenie dodatku, który zmienia “cokolwiek” jest w zasadzie niemożliwe.

    Nie no, bez przesady, że zamknięty :slight_smile: Jak ktoś ma jeszcze jakieś przemyślenia na ten temat, to ja bardzo chętnie posłucham. W ogóle fajnie by było jakby ludzie częściej dzielili się swoimi “genialnymi” pomysłami - w 99% przypadków pewnie będą to nietrafione pomysły, ale zawsze zostaje ten 1%. A może ktoś się zainspiruje i wpadnie na coś lepszego, bez tak wielu minusów.

    Tak jak napisałem - chodzi o system rozszerzeń dla jednej aplikacji. Tak samo jak w radiancie zdefiniowane są regiony za pomocą render_region, tak samo można to zrobić jeżeli chodzi o id/klasy w htmlu.

    Oczywiście, zgadzam się. Aczkolwiek semantycznie pocięty HTML mam w aplikacji out of the box, a powyższe radiantowe rozwiązanie lub jakiekolwiek inne tego typu dopasowania muszą zostać dopisane do aplikacji. Spójrz jeszcze raz na kod, do którego dałem linka - w widokach w wielu miejscach są powstawiane metody do odpowiedniego opisania owych widoków. Tutaj jest dokładnie tak samo jak w przypadku main_menu - jeżeli ktoś usunie dany region, albo zmieni jego nazwę w kolejnej wersji radianta, to rozszerzenia, które na tym bazowały przestaną działać. Cudów nie ma :wink:

    Kwestia jest taka, że w rozwiązaniu z parsowaniem HTMLa nie trzeba dodatkowych wstawek w kodzie.

    Nigdy nie twierdziłem, ze jest inaczej :wink:

    UPDATE:
    Przeczytaj proszę uważnie pierwszego posta. Tam jest podany przykład z radianta, który dodaje do zakładek w panelu admina dodatkową zakładkę. Jest do tego napisane API, tak samo jak do wspomnianych powyżej regionów.

    Nie ma za dużo dokumentacji do radianta, ale możesz obczaić jak to działa w specach:

    W żadnym miejscu nie pisałem, że chodzi o mechanizm do jakiejkolwiek aplikacji, ani o to, że będzie to można podpiąć do każdego kodu.

    Jasne, sorry, to taka moja lekka “nadinterpretacja”. Po porostu myślałem o tym rozwiązaniu w kategoriach “general purpose” :slight_smile:
    Dosyć dobrze jestem zaznajomiony z tym, jak tego rodzaju (ogólne) rozszerzenia działają w platformie Eclipse (np. plugin dodaje coś do menu, dodaje jakąś akcję w pasku akcji, etc.), stąd może moje “skrzywienie”. Z drugiej strony dziwnym byłoby również wymyślanie systemu rozszerzeń, które wykorzystywane byłyby tylko w jednej aplikacji - najprościej przecież chyba uczynić je po prostu jej częścią…

    Jeśli zaś chodzi o samego Radianta, to przyznam szczerze, że zraził mnie ten system, właśnie swoimi rozszerzeniami. Co prawda bawiłem się nim prawie 2 lata temu, ale nie sądzę, aby w tym zakresie dużo się zmieniło. Korzystałem zdaje się z własnych definicji “tagów” - zrozumienie logiki tychże było nie lada wyzwaniem (chociażby ze względu na minimalną ilość dokumentacji).

    Natomiast Twój pomysł jest o tyle lepszy, że nie wymyśla tego co Rails już i tak zapewnia, czyli właściwej separacji MVC, etc. Przy zachowaniu minimalnych wymogów (obecność pewnego węzła w HTMLu) można sobie coś “przezroczyście” dodać. Dlatego pomysł mi się spodobał, choć może nie do końca wyraziłem to w tamtym poście :slight_smile:

    Jak będę miał trochę czasu to może szybko coś takiego zaimplementuję. Jako taki proof of concept i sprawdzenie jak to będzie działać.

    Nie wiem jakie będą z tym problemy, ale na pewno nie wydajność. :slight_smile:

    I po około 3 latach:

    [quote=slawosz]I po około 3 latach:
    https://github.com/railsdog/deface[/quote]
    Miałem już się czepić nekromancji, ale skoro podlinkowałeś Deface – jak dla mnie najlepszą bibliotekę drugiej połowy 2011 – to ok :wink:

    Szkoda, że jak zwykle mi się nie chciało zabrać do pracy :wink:

    A mógłbyś napisać o niej szerzej w kontekście swoich doświadczeń?

    Poza “tak właśnie powinna być zrobiona customizacja istniejących/dostarczonych/engineowych widoków” to mogę zaoferować prezkę na WRUGu, leżącą na dysku od listopada :wink:

    Tak swoją drogą, po przeczytaniu tego wątku jeszcze raz, rada dla innych: jak masz jakiś pomysł na plugin, to ostatnią rzeczą jaką powinieneś zrobić jest pytanie o opinie na ten temat na forum :stuck_out_tongue:

    A jako epilog: Deface doskonale gra z Radiantem. Nie słyszałem o enginie z którym ten gem miałby kłopoty :smiley:

    Drogus: jasne, trzeba najpierw zrobić choćby i proof-of-concept, według konkretnej wizji i pomysłu, a dopiero potem dyskutować na forum. Design by commitee rzadko się sprawdza.