17

Tôi nghĩ rằng tôi muốn tìm ra một cách khéo léo để mở rộng ApplicationController trong một gem Rails 3.x.Tôi có thể mở rộng ApplicationController bằng đá quý bằng cách nào?

Trong đá quý của tôi lib/my_namespace/my_controller.rb, tôi đã có:

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 

nhưng khi Gem được nạp, app/controllers/application_controller.rb chưa được nạp, vì vậy nó không thành công:

/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) 

Là một workaround, tôi đã định nghĩa ApplicationController trong đá quý của tôi lib/gem_namespace/application_controller.rb là:

class ApplicationController < ActionController::Base 
end 

I giả định rằng mặc dù tôi đã định nghĩa nó ở đó, nó sẽ được định nghĩa lại trong ứng dụng Rails 3 của tôi là app/controllers/application_controller.rb, như vậy cả hai bộ điều khiển trong ứng dụng mở rộng ApplicationController và bộ điều khiển mở rộng MyNamespace::MyController sẽ trực tiếp hoặc gián tiếp mở rộng ApplicationController được định nghĩa trong app/controllers/application_controller.rb.

Tuy nhiên, chúng tôi nhận thấy rằng sau khi tải đá quý, bộ điều khiển mở rộng ApplicationController không thể truy cập các phương thức được xác định trong app/controllers/application_controller.rb. Ngoài ra, mô-đun ApplicationHelper(app/helpers/application_helper.rb) không còn được tải bởi các mô-đun trợ giúp khác.

Làm thế nào tôi có thể mở rộng ApplicationController trong bộ điều khiển trong đá quý của tôi với mục đích xác định một before_filterafter_filter đến và sử dụng initialize để truy cập tên của lớp để xác định lớp mô hình liên quan của nó sau đó có thể lưu trữ và sử dụng trong phương pháp của nó?

Cập nhật 2012/10/22:

Đây là những gì tôi đã đưa ra:

Trong 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 

và trong 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 

Trả lời

5

Here is a Gist cho biết cách truy cập lớp học của lớp con và lưu nó trong một biến mẫu và truy cập nó trong bộ lọc trước và sau. Nó sử dụng phương pháp include.

+0

Tuyệt vời! Bao gồm một mô-đun thực sự là ý tưởng tốt nhất, sau đó. Cảm ơn sự giúp đỡ của bạn! –

+0

[Cuộc trò chuyện liên quan trong Diễn đàn Rails] (https://web.archive.org/web/20130216193936/http://railsforum.com/viewtopic.php?pid=153813) –

8

Đối specifi này c loại chức năng tôi sẽ khuyên bạn nên tạo một module trong đá quý của bạn và bao gồm các module rằng trong điều khiển ứng dụng của bạn

class ApplicationController < ActionController::Base 
    include MyCoolModule 
end 

Để thêm trước khi bộ lọc, vv (thêm video này vào mô-đun của bạn)

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

Cập nhật: bạn có thể chỉ làm base.before_filter :my_method sạch hơn.

+0

sẽ bao gồm một module thực sự làm việc trong trường hợp này? Bạn có thể mở rộng về cách bạn sẽ nhận được tên của lớp con vì không có lớp con và cách bạn sẽ tận dụng lợi thế của before_filter và after_filter? Hãy xem xét một câu hỏi khác và nếu bạn có thể mở rộng những câu hỏi đó, vui lòng làm rõ. Nếu không, tôi sẽ giả định nó không phải là một lựa chọn. Cảm ơn! –

+0

Chỉ cần làm rõ trong dòng cuối cùng của câu hỏi mà tôi cần sử dụng before_filter, after_filter và truy cập tên của lớp con. –

0

Tôi có thể tham chiếu ApplicationController bằng trình khởi tạo lại trình khởi tạo.

đang đá quý mà lớp con/tài liệu tham khảo ApplicationController:

class GemApplicationController < ApplicationController 
    before_filter :method_to_call 

    def method_to_call 
    #your code here 
    end 
end 

đá quý đang callback để tạo ra bộ điều khiển subclassed:

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

rails_app/config/initializers/gem_name.rb

GemName.load_gem_application_controller 

Sau đó, có bộ điều khiển sử dụng chức năng này lớp con GemApplicationController

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

Tôi đã học được rằng nên sử dụng các mô-đun. Bạn chỉ có thể kế thừa từ một lớp cha (trực tiếp), nhưng bạn có thể bao gồm/mở rộng các mô-đun nhiều như bạn muốn. –

+1

Cảm ơn Gary. Tôi đã kết thúc việc thực hiện với một mô-đun là tốt. –

2

Sự thật là nhiều đơn giản hơn nhiều và linh hoạt.

Thêm vào lib/engine.rb này: class Engine < Rails::Engine; end

Và sau đó chỉ cần sử dụng:

ActionController::Base.class_eval do 

    include SomethingFromMineGemModule 

    # or: 
    def hello_from_gem 
    'Hey people!' 
    end 

end