Generowanie wyniku kontrolera i jego widoku do zmiennej

Witajcie, czy jest możliwość wygenerowania w jednym kontrolerze kilku widoków które korzystają ze swoich kontrolerów i modeli a następnie przypisanie ich do zmiennych ?

ogólnie chodzi mi o to aby mieć 2-3 kontrolery z których każdy odpowiada np. za inny sposób pokazania jakiegoś fragmentu strony (np. listy zdjęć / czy artykułów)

Następnie chciałbym mieć jeden widok w którym mam np. 30 różnych miejsc i ręcznie do każdego z tych miejsc mógłbym wstawiać wygenerowany kod z kontrolerów.

Problem z jakim się zmagam wziął się stąd że mam na stronie głównej top 5 czytanych artykułów, jakąś mini galerię najciekawszych zdjęć i jeszcze kilka innych (bloków sekcji) - a każdy z tych elementów może występować w kilkunastu różnych wersjach które się zmieniają w zależności od pory roku dnia nastroju admina :wink: - chciałbym to jakoś sensownie ogarnąć.

Akcje kontrolera jest ściśle związana z zapytaniem HTTP i nie można ich ręcznie wywoływać.

Renderowanie widoku spoza kontrolera jest możliwe przy pomocy https://github.com/yappbox/render_anywhere i prawdopodobnie będzie wbudowane w Rails5.

Popularnym rozwiązaniem komponentów jest https://github.com/apotonick/cells

Może użyj content_for. Chociaż przy dużej liczbie obszarów może być kłopotliwe.

@gogiel rouzmiem że render_anywhere może nam wygenerować jedynie widok ? o kontrolerze czy modelu które pobiorą specjalne dane nie mam mowy ?

kurcze zastanawiam się jak w takim razie to funkcjonuje w cms-ach gdzie mamy w jednym katalogu kilkanaście pluginów (widok kontroler model) i wstawiamy je w odpowiednim miejscu na stronę… via joomla, wordpress etc.

@mgebala8 możesz tworzyć dla każdego komponentu osobne klasy - nie ma problemu, żeby gadały one z modelami.
Kontroler to jedynie pośrednik pomiędzy HTTP a Ruby. Zamiast klas od zera można użyć Rails Cells, które dają MVC.

W joomli controller mają tylko komponenty i możesz mieć w danym widoku tylko jedną akcje jednego kontrolera. Oprócz tego możesz mieć w danym widoku umieszczony moduł ktory najwyżej będzie korzystał z akcji swojego kontrolera po ajaxie. Pluginy nie mają osobnych kontrollerów dokładasz nimi tylko coś do komponentów. Jako moduł możesz użyć w railsach partiala. Dane z modelu musisz pobrać w danej akcji kontrolera lub po ajaxie z dowolnego kontrolera.

CMSy mają często inną strukturę niż MVC. Ale problem jednego rendera per strona jest dosyć częśty u ludzi przychodzących z innych języków więc postaram się opisać koszerne rozwiązanie tego problemu na przykładzie widgetów właśnie.

Najprostszym rozwiązaniem jest używanie partiali. Te można spokojnie generować/renderować na kopy.
Kod który normalnie wylądowałby wtedy w kontrolerze pakujemy do osobnego zestawu klas - Widżetów.
Tworzymy po prostu w app katalog widgets i po restarcie zerwera pakujemy tam wszelkie widgety. Na koniec dodajemy helpera który pomoże nam tworzyć wszystkie widgety i renderować je w widokach lub kontrolerach.

Np:

page_count_widget.rb


class GuestCounterWidget
  def initialize(options={})
    @options=options
  end

  # tu zwracamy ścierzkę do widoku widgeta. Należy pamiętać że musi się ona zaczynać od _ np. _black.html.erb
  def view
    
    "widgets/guest_counter/#{@options[:style] || 'default'}"
  end

  # Tu zwracamy zmienne lokalne dostępne w widoku takiego widgeta. Uzywamy tylko zmiennych lokalnych (zmiast zmiennych instancji zaczynających się od @ żeby upewnić się że nie będzie żadnych interakcji pomiędzy widgetami, nawet jeśli będą to różne instancje widgetów tej samej klasy tylko z innymi opcjami.
  def prepare
     # tutaj przygotowywujemy wszystkie mozliwe dane ptrzebne widgetowi i zwracamy jako hash
     {foo: 1, bar: 'baz', opt: @options[:size]}
  end
end
     

widget_helper.rb pomoże nam renderować widgety w widokach in kontrolerach.

class WidgetHelper
  def render_widget(widget_class, options={})
    widget = widget_class.new(options)
    render partial: widget.view, locals: widget.prepare
  end

po czym w widoku lub kontrolerze najnormalniej w świecie:

  render_widget GuestCounterWidget, style: 'black'

  # albo bardziej rozbudowany przykład renderujemy widgety dla danego slota

  slot.widgets.each do |widget_name, options|
    render_widget widget_name.constantize, options
  end

Używając partiali i wyłączając logikę renderowania do osbnych klas widgetów można bez problemu sobie poradzić z modułowymi stronami bez potrzeby bardziej skomplikowanych rozwiązań w rodzaju cells.

Należy zwrócić uwagę że klasa “Widget” pełni tutaj rolę kontrolera we wzorcu MVC, nie zawsze kontroler musi się kontroler nazywać :smiley:

PS. Kod pisany na metaforycznym kolanie więc moga być literówki albo lekkie przeinaczenia ale ma głónei na celu wskazanie sposobu rozwiązania problemu. W razie problemów lub dalszych pytań pisz w tym wątku postaram się pomóc :slight_smile:

4 Likes

@swistak84 - dziękuję Ci bardzo za odpowiedź postaram się to przetestować przez weekend :wink:

Nie ma problemu. Jeżeli potrzebujesz naprawdę zwrócić zawartość widoku do łancucha jeste też metoda render_to_string ale przez dekadę pracy w railsach użyłem jej chyba tylko raz.

Po opisie zarówno problemu jak i odpowiedzi mam wrażenie, że tak naprawdę szukasz tego (cellek). W twoim wypadku metody renderujące będą lądowały różne widoki, ale dane i zestaw metod-helperów będzie ten sam. Daj znać jeśli potrzebujesz wyjaśnienia jak z tego korzystać :slight_smile: