2013-05-09 11 views
6

Mam nieco dziwne wymagania dla nowej aplikacji Rails. Potrzebuję zbudować aplikację, w której wszystkie trasy są zdefiniowane w wielu przestrzeniach nazw (pozwól mi wyjaśnić). Chcę mieć aplikację, w której przedmioty szkolne (matematyka, angielski, etc) są przestrzenie nazw:Dynamiczne kontrolery z obsługą nazw w/awaryjne w szynach

%w[math english].each do |subject| 
    namespace subject.to_sym do 
    resources :students 
    end 
end 

To jest wielki i to działa, ale wymaga mnie do stworzenia przestrzeni nazw StudentsController dla każdego pacjenta, co oznacza, gdybym dodaj nowy temat, a następnie muszę utworzyć nowy kontroler.

Co chciałbym jest stworzenie Base::StudentsController a jeśli, powiedzmy, że Math::StudentsController istnieje wtedy będzie on używany, a jeśli jej nie ma, to możemy dynamicznie utworzyć ten kontroler i dziedziczą Base::StudentsController.

Czy to jest coś, co jest możliwe? Jeśli tak, to w jaki sposób mam to wdrożyć?

+0

Po prostu utworzę wszystkie kontrolery nazw i będą dziedziczyć po kontrolerze bazowym. Następnie w razie potrzeby zastąp. –

+0

Rozumiem, że mogę to zrobić, ale to da mi wiele kontrolerów, które będą zawierać deklarację klasy. Ponadto, jeśli mam 20 kontrolerów i chcę dodać nowy "obiekt", to potrzebuję skopiować 20 kontrolerów. –

+0

Racja, widzę. Czy jest to faktyczne wymaganie, czy jest to tylko eksperyment? Ponieważ płynność "matematyki", "angielskiego" i cokolwiek innego przychodzi, nie nadaje się do wyświetlania nazw. A co z odwracaniem tego (uczniowie/matematyka)? W tym momencie naprawdę masz działanie kontrolera. Teraz * to * może poprawić dynamikę. –

Odpowiedz

1

skończyło się na pisanie jakiś niestandardowy logiki do ActionDispatch::Routing::RouteSet::Dispatcher.controller_reference . Próbuję wyszukać wszystkie stałe wymagane dla danego kontrolera i utworzyć je, jeśli ich brakuje. Ten kod to FAR od ideału, więc możesz edytować w/ulepszenia.

class ActionDispatch::Routing::RouteSet::Dispatcher 

    private 

    def controller_reference(controller_param) 
    const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller" 

    obj = Object 
    const_name.split('::').each do |cn| 
     begin 
     obj = obj.const_get(cn) 
     rescue 
     if obj == Object 
      obj = obj.const_set(cn, Class.new(ApplicationController)) 
     else 
      puts "Creating #{obj}::#{cn} based on Generic::#{cn}" 
      obj = obj.const_set(cn, Class.new("Generic::#{cn}".constantize)) 
     end 
     end 
    end 

    ActiveSupport::Dependencies.constantize(const_name) 
    end 

end 
+0

Spójrz jeszcze raz na [moja odpowiedź] (http://stackoverflow.com/a/16511066/712765) i myślę, że spodoba ci się jeszcze bardziej. –

3

z dróg określonych w ten sposób:

%w[math english].each do |subject| 
    scope "/#{subject}" do 
    begin 
     "#{subject.camelcase}::StudentsController".constantize 
     resources :students, controller: "#{subject}::students", only: :index 
    rescue 
     resources :students, controller: "base::students", only: :index 
    end 
    end 
end 

rake routes wyjścia:

students GET /math/students(.:format) base::students#index 
     GET /english/students(.:format) english::students#index 

Jeśli angielski/students_controller.rb jest obecny i matematyczne/students_controller. jest nieobecny.

+0

To wydaje się dość szczegółowe i trzeba to zrobić dla _every_ pojedynczego zasobu w mojej aplikacji. –

+0

Mogę tylko zaproponować metodę taką jak ta http://pastebin.com/rLLZpCYx, ale myślę, że nie będziesz zadowolony. –

3

Wierzę, że to będzie to zrobić:

%w[math english].each do |subject| 
    namespace subject.to_sym do 
     resources :students 
    end 
    end 

    match ':subject/students(/:action(/:id))' => 'base/students' 

Z tych połączonych tras /math/students idzie do Math::StudentsController, /english/students/ idzie do English::StudentsController i wszystkie inne przedmioty (np /physics/students i /cs/students) Idź Base::StudentsController.

co moim zdaniem jest dokładnie co chcesz i tylko dodaje jedną linię kodu do oryginalnego rozwiązania.

+0

Próbuję dostosować swoją odpowiedź do mojej sytuacji, która jest bardzo podobna, z tym wyjątkiem, że mam wiele niestandardowych tras w mojej przestrzeni nazw. Otrzymuję błąd "niezainicjowane stałe portale" V1 "i nie jestem pewien, czy w pełni rozumiem, jak działa twoje rozwiązanie. Moja konfiguracja jest podobna, z tym że moja dynamiczna przestrzeń nazw jest zagnieżdżona w podstawowej przestrzeni nazw, która się nie zmienia. Mam zestaw kontrolerów, które są nazwane i chcą, aby każda dynamiczna pod-przestrzeń nazw odwoływała się do tych kontrolerów. – ACIDSTEALTH

+0

Na przykład, '/ portals/example1' i'/portals/example2' powinny przejść do 'general # home' w' controllers/portals/general_controller.rb' – ACIDSTEALTH

+0

@ACIDSTEALTH, który jest zbyt skomplikowany do dyskusji w komentarzach. Zadaj nowe pytanie, jeśli nie dostarczy Ci potrzebnych informacji. Odwołaj się do tego pytania w nowym pytaniu i wyjaśnij, dlaczego odpowiedzi tutaj nie działają. Oczywiście, najpierw przeczytaj * wszystkie * odpowiedzi na to pytanie. –

3

Aby przekształcić swoje wymagania:

  1. Minimalny deklaracji za parę przedmiotem/zasobów
  2. Zastosowanie odpowiedniego regulatora (Math::StudentsController) jeśli istnieje, w przeciwnym razie użyj podstawowy kontroler (StudentsController)

Szyny spodziewa każda trasa ma dedykowanego kontrolera i tak naprawdę nie ma dobrego sposobu na popieranie drugiego wymogu. Tak, to w jaki sposób to zrobić:

Dynamicroutes::Application.routes.draw do 
    SUBJECTS = [ "math", "english", "chemistry" ] 
    RESOURCES = [ "assignments", "students" ] 

    class DedicatedSubjectResourceControllerConstraint 
    def initialize(subject, resource) 
     @subject = subject 
     @resource = resource 
    end 

    def matches?(request) 
     begin 
     defined?("#{@subject.capitalize}::#{@resource.capitalize}") 
     return true 
     rescue NameError 
     Rails.logger.debug "No such class: #{@subject.capitalize}::#{@resource.capitalize}" 
     return false 
     end 
    end 
    end 

    class ValidSubjectConstraint 
    def matches?(request) 
     return SUBJECTS.include?(request.path_parameters[:subject]) 
    end 
    end 

    SUBJECTS.each do |subject| 
    RESOURCES.each do |resource|  
     namespace subject, :constraints => DedicatedSubjectResourceControllerConstraint.new(subject, resource) do 
     resources resource 
     end 
    end 
    end 

    RESOURCES.each do |resource| 
    scope "/:subject", :constraints => ValidSubjectConstraint.new do 
     resources resource 
    end 
    end 
end 
3

To brzmi jak wyrażeniem const_missing.Jeśli to, co chcesz zrobić, to

stworzyć bazę :: StudentsController

i jeżeli powiedzmy Math :: StudentsController istnieje

następnie będzie on używany

i jeśli nie istnieje, możemy dynamicznie utworzyć ten kontroler i odziedziczyć po Base :: StudentsController

Możesz to osiągnąć przez dodanie dynamicznego wyszukiwania stałego (const_missing) i dynamicznej definicji stałej z dziedziczeniem (Object.const_set).

Wyobrażam sobie coś takiego; z kilku poprawek i bardziej rygorystycznej kontroli, będzie działać:

# initializers/dynamic_controllers.rb 

class ActionDispatch::Routing::RouteSet 

    SUBJECTS = [ "math", "english", "chemistry" ] 

    def const_missing(name, *args, &block) 
    if SUBJECTS.any?{ |subject| name.include? subject.uppercase } 
     Object.const_set name, Class.new(Base::StudentsController) 
    else 
     super 
    end 
    end 

end 

To dodamy dynamiczne stale wyszukiwań do ActionDispatch::Routing::RouteSet, z którego Dynamicroutes::Application.routes dziedziczy, więc niezdefiniowanych stałymi Dynamicroutes::Application.routes.draw wygeneruje odpowiednie zajęcia podklasy z Base::StudentsController.

+0

Dodałem ten inicjator i ponownie uruchomiłem moją aplikację, ale wydaje się, że nie jest ona wywoływana. Jakieś pomysły? –

+0

Nie jestem pewien. Pozwól mi stworzyć obojętną aplikację do zabawy. –

+0

Zrobiłem to blisko pracy, używając 'def Object.const_missing'. – graysonwright

1

Wszystkie pomoce do routingu, takie jak resources, scope, itp. Są po prostu funkcjami wewnątrz tras aplikacji. można po prostu zdefiniować funkcję niestandardową następująco:

YourApplication.routes.draw do 

    # Let's define a custom method that you're going to use for your specific needs 
    def resources_with_fallback(*args, &block) 
    target_module  = @scope[:module].camelize.constantize 
    target_controller = "#{args.first.to_s}_controller".camelize 
    fallback_controller = args.last.delete(:fallback).to_s.camelize.constantize 

    # Create the target controller class 
    # using fallback_controller as the superclass 
    # if it doesn't exist 
    unless target_module.const_defined?(target_controller) 
     target_module.const_set target_controller, Class.new(fallback_controller) 
    end 

    # Call original resources method 
    resources *args, &block 
    end 

    # Now go ahead and define your routes! 

    namespace "test" do 
    namespace "new" do 
     # Use our custom_resources function and pass a fallback parameter 
     custom_resources :photos, :fallback => 'base/some_controller' 
    end 
    end 

end 

Testowałem to w Rails 3.2, ale powinien działać równie dobrze we wszystkich wersjach 3.x.

Dołączyłem brak czeków zerowych lub begin/rescue bloków w dowolnym miejscu. Ponieważ masz zamiar używać tej niestandardowej funkcji tylko wtedy, gdy jest to wymagane, zakładam, że przekażesz poprawne i niezbędne parametry. Jeśli powiemy, że nie istnieje kontroler fallback, wolałbym, aby przetwarzanie tras zakończyło się niepowodzeniem z wyjątkiem, a nie próbowało go obsłużyć.

Edit: literówka w argumentach funkcji

Edit 2: Zapomniałem &block w argumentach funkcji

Edit 3: Dodaj "_controller" do target_controller zmiennej

+0

Uwaga: '@scope [: module]' jest wbudowaną zmienną w klasie Route w Railsach. Używam go, aby uzyskać bieżący moduł, w którym się zagnieżdżamy. Ten sam "zasięg" jest również używany przez funkcję 'resources' i większość innych funkcji w pliku tras. – Subhas

+0

To nie działa dla OP lub dla mnie. To nie działa dla OP, ponieważ chce tworzyć stałe najwyższego poziomu, więc '@skope [: module] .camelize.constantize' generuje błąd (niezainicjowana stała). Nie działa to dla mnie, ponieważ nawet jeśli zdefiniowano 'Test', to się nie powiedzie, ponieważ' @scope [: module] '== 'test/new', a nie 'test'. Tak, możesz naprawić te problemy, ale teraz jest to skomplikowane i generuje fałszywe klasy, aby mogły odbierać wiadomości generowane meta-programowo. Za dużo magii. –

Powiązane problemy