17

Pomyślałem, że wymyślę zręczny sposób na rozszerzenie ApplicationController w gemie Rails 3.x.Jak mogę rozszerzyć ApplicationController w klejnot?

w moim gem lib/my_namespace/my_controller.rb miałem:

class MyNamespace::MyController < ApplicationController 

    before_filter :some_method 
    after_filter :another_method 

    def initialize 
    # getting classname of the subclass to use for lookup of the associated model, etc. 
    # and storing the model_class in an instance variable 
    # ... 
    end 

    # define :some_method, :another_method, etc. 
    # ... 

private 
    attr_accessor :subclass_defined_during_initialize # etc. 

    # etc. 
end 

ale gdy Gem załadowaniu app/controllers/application_controller.rb nie jest jeszcze załadowany, więc to się nie powiedzie:

/path/to/rvm/gemset/gems/activesupport-3.2.6/lib/active_support/dependencies.rb:251: 
in `require': cannot load such file -- my_gem_name/application_controller (LoadError) 

Jako obejście, miałem zdefiniowane ApplicationController w moim klejnocie:jako:

class ApplicationController < ActionController::Base 
end 

I założyłem, że chociaż zdefiniowałem to tam, to zostanie ono ponownie zdefiniowane w aplikacji mojej aplikacji Rails 3 app/controllers/application_controller.rb, tak że oba kontrolery w aplikacji, która rozszerzyła ApplicationController i kontrolery, które rozszerzyły MyNamespace::MyController, bezpośrednio lub pośrednio rozszerzają ApplicationController zdefiniowany w app/controllers/application_controller.rb.

Zauważyliśmy jednak, że po załadowaniu klejnotu kontrolery rozszerzające ApplicationController nie mogły uzyskać dostępu do metod zdefiniowanych w app/controllers/application_controller.rb. Ponadto moduł ApplicationHelper(app/helpers/application_helper.rb) nie był już ładowany przez inne moduły pomocnicze.

Jak mogę przedłużyć ApplicationController w kontrolerze w moim gem dla celów definiowania before_filter i after_filter i korzystanie initialize dostęp do nazwy klasę, aby określić klasę powiązanego modelu, który mógłby następnie przechowywać i wykorzystywać w swoich metodach?

Aktualizacja 22.10.2012:

Oto co wymyśliłem:

W lib/your_gem_name/railtie.rb:

module YourGemsModuleName 
    class Railtie < Rails::Railtie 
    initializer "your_gem_name.action_controller" do 
    ActiveSupport.on_load(:action_controller) do 
     puts "Extending #{self} with YourGemsModuleName::Controller" 
     # ActionController::Base gets a method that allows controllers to include the new behavior 
     include YourGemsModuleName::Controller # ActiveSupport::Concern 
    end 
    end 
end 

aw lib/your_gem_name/controller.rb:

module YourGemsModuleName 
    module Controller 
    extend ActiveSupport::Concern 

    # note: don't specify included or ClassMethods if unused 

    included do 
     # anything you would want to do in every controller, for example: add a class attribute 
     class_attribute :class_attribute_available_on_every_controller, instance_writer: false 
    end 

    module ClassMethods 
     # notice: no self.method_name here, because this is being extended because ActiveSupport::Concern was extended 
     def make_this_controller_fantastic 
     before_filter :some_instance_method_available_on_every_controller # to be available on every controller 
     after_filter :another_instance_method_available_on_every_controller # to be available on every controller 
     include FantasticStuff 
     end 
    end 

    # instance methods to go on every controller go here 
    def some_instance_method_available_on_every_controller 
     puts "a method available on every controller!" 
    end 

    def another_instance_method_available_on_every_controller 
     puts "another method available on every controller!" 
    end 

    module FantasticStuff 
     extend ActiveSupport::Concern 

     # note: don't specify included or ClassMethods if unused 

     included do 
     class_attribute :class_attribute_only_available_on_fantastic_controllers, instance_writer: false 
     end 

     module ClassMethods 
     # class methods available only if make_this_controller_fantastic is specified in the controller 
     def some_fanastic_class_method 
      put "a fantastic class method!" 
     end 
     end 

     # instance methods available only if make_this_controller_fantastic is specified in the controller 
     def some_fantastic_instance_method 
     puts "a fantastic instance method!" 
     end 

     def another_fantastic_instance_method 
     puts "another fantastic instance method!" 
     end 
    end 
    end 
end 

Odpowiedz

5

Here is a Gist który pokazuje, jak uzyskać dostęp do klasy podklasy i zapisać ją w zmiennej instancji i uzyskać do niej dostęp w filtrach przed i po. Używa metody include.

+0

Awesome! Włączenie modułu jest naprawdę najlepszym pomysłem. Bardzo dziękuję za Twoją pomoc! –

+0

[Powiązana rozmowa na forum Railsowym] (https://web.archive.org/web/20130216193936/http://railsforum.com/viewtopic.php?pid=153813) –

8

Dla ten specyfik c rodzaj funkcjonalności polecam stworzenie modułu w swoim gem i te moduły w kontrolerze aplikacji

class ApplicationController < ActionController::Base 
    include MyCoolModule 
end 

Aby dodać przed filtrami, etc (dodać do modułu)

def self.included(base) 
    base.send(:before_filter, my_method) 
end 

Update: możesz być w stanie zrobić tylko base.before_filter :my_method, który jest czystszy.

+0

Czy w tym moduł naprawdę działa w tym przypadku? Czy mógłbyś rozwinąć swoją nazwę podklasy, skoro nie ma podklasy i jak korzystać z before_filter i after_filter? Spójrz jeszcze raz na to pytanie i jeśli możesz je rozwinąć, wyjaśnij. W przeciwnym razie, zakładam, że to nie jest opcja. Dzięki! –

+0

Po prostu wyjaśniono w ostatnim wierszu pytania, które muszę użyć before_filter, after_filter i dostęp do nazwy podklasy. –

0

Udało mi się odwołać ApplicationController z wywołania zwrotnego inicjalizatora.

kod gem że podklas/referencje ApplicationController: Kod

class GemApplicationController < ApplicationController 
    before_filter :method_to_call 

    def method_to_call 
    #your code here 
    end 
end 

gem oddzwanianie do tworzenia podklasy kontrolera:

module GemName 
    def self.load_gem_application_controller 
    require "path/to/gem_application_controller" 
    end 
end 

rails_app/config/inicjalizatory/gem_name.rb

GemName.load_gem_application_controller 

Następnie mają kontrolery, które używają tej podklasy funkcjonalności GemApplicationController

class SpecialCaseController < GemApplicationController 
    # this will inherit from the gem's controller, 
    # which inherits from the rails_app ApplicationController 
end 
+0

Nauczyłem się, że lepiej jest używać modułów. Możesz odziedziczyć tylko jedną klasę nadrzędną (bezpośrednio), ale możesz dołączyć/rozszerzyć moduły tak, jak chcesz. –

+1

Dzięki Gary. Skończyłem także na implementacji z modułem. –

2

Prawda jest o wiele prostsza i bardziej elastyczna.

Dodaj do lib/engine.rb to: class Engine < Rails::Engine; end

A potem po prostu użyć:

ActionController::Base.class_eval do 

    include SomethingFromMineGemModule 

    # or: 
    def hello_from_gem 
    'Hey people!' 
    end 

end 
Powiązane problemy