通过外部宝石增强模型 [英] Augmenting a model from an external gem

查看:74
本文介绍了通过外部宝石增强模型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我们的网站中使用 refinerycms ,让技术含量较低的人员更新内容.在gem内,它们具有Page类,该类映射站点上的每个顶级页面.我想在此Page类上使用acts_as_taggable gem.现在,我可以将acts_as_taggle声明直接添加到page.rb文件中,但是随后我必须维护一个单独的git repo来跟踪我的版本与正式版本之间的差异.

基于SO上的其他一些问题,我创建了一个初始化程序和扩展名,如下所示:

lib/page_extensions.rb:

module Pants
  module Extensions

    module Page
      module ClassMethods
        def add_taggable
          acts_as_taggable
        end
      end

      def self.included(base)
        base.extend(ClassMethods).add_taggable
      end

    end

  end
end

config/initializers/pants.rb

require 'page_extensions'

Page.send :include, Pants::Extensions::Page

app/views/layouts/application.html.erb

...
Tags: <%= @page.tag_list %>

我第一次从服务器请求页面时,它会正确输出页面上的所有标签.但是,如果我单击刷新,我会得到一个NoMethodError,指示未定义tag_list.

我是Rails的新手,所以也许我的假设是错误的,但是我希望对Page.send的调用将对Page类而不是对该类的特定实例进行永久更改.那么如何在每次请求时将act_as_taggable添加到Page类中?

解决方案

您需要将module_eval代码放入config.to_prepare do块中.最简单的位置是在config/application.rb中或创建引擎.这些代码是相同的,除了它不仅在您每次运行站点时都执行(不仅是第一次)(特别是适用于开发模式),并且仅在初始化过程(也称为文件)插入config.before_initialize do块之前执行. >

config.to_prepare很重要的原因是因为在开发模式下,每个请求都会重新加载代码,但初始化器通常不会.这意味着要在其上运行module_eval的Page仅将module_eval运行一次,但将在每次请求时重新加载自身. config.to_prepare是一次可以每次运行的Rails挂钩,为这种情况提供了极大的便利.

config/application.rb方法

class Application < Rails::Application
  # ... other stuff ...

  config.before_initialize do
    require 'page_extensions'
  end

  config.to_prepare do
    Page.send :include, Pants::Extensions::Page
  end
end

引擎方法

如果您不想修改config/application.rb,则可以在Refinery CMS中创建如下所示的vendor/engines/add_page_extensions/lib/add_page_extensions.rb:

require 'refinery'

module Refinery
  module AddPageExtensions
    class Engine < Rails::Engine

      config.before_initialize do
        require 'page_extensions'
      end

      config.to_prepare do
        Page.send :include, Pants::Extensions::Page
      end

    end
  end
end

如果使用引擎方法,则还需要创建vendor/engines/add_page_extensions/add_page_extensions.gemspec,其中应包含一个简单的gemspec:

Gem::Specification.new do |s|
  s.name = 'add_page_extensions'
  s.require_paths = %w(lib)
  s.version = 1.0
  s.files = Dir["lib/**/*"]
end

然后在您的Gemfile中添加以下行:

gem 'add_page_extensions', :path => 'vendor/engines'

如果采用引擎方法,您可能希望将所有逻辑放入引擎的lib目录中,包括Pants::Extensions::Page代码.

希望这会有所帮助

I'm using refinerycms in our site to let the less technical staff update content. Inside the gem, they have a Page class that maps each top level page on the site. I'd like to use the acts_as_taggable gem on this Page class. Now I can add the acts_as_taggle declaration directly to the page.rb file, but then I'd have to maintain a separate git repo to track differences between my version and the official release.

Based on some other questions here on SO I created an initializer and extension like so:

lib/page_extensions.rb:

module Pants
  module Extensions

    module Page
      module ClassMethods
        def add_taggable
          acts_as_taggable
        end
      end

      def self.included(base)
        base.extend(ClassMethods).add_taggable
      end

    end

  end
end

config/initializers/pants.rb

require 'page_extensions'

Page.send :include, Pants::Extensions::Page

app/views/layouts/application.html.erb

...
Tags: <%= @page.tag_list %>

The first time I request a page from the server it correctly outputs all the tags on the page. However, if I hit refresh I instead get a NoMethodError indicating that tag_list is undefined.

I'm new to rails so perhaps my assumptions are wrong, but I expected that call to Page.send would make a permanent change to the Page class rather than to a specific instance of the class. So how would I get the acts_as_taggable added to the Page class on each request?

解决方案

You will need to put your module_eval code into a config.to_prepare do block. The easiest place to do this is in config/application.rb or to create an engine. The code is identical except it executes every time you run the site not just the first time (which especially applies to development mode) and code which only executes before the initialisation process (aka requiring files) into a config.before_initialize do block.

The reason that config.to_prepare is important is because in development mode, the code is reloaded on every request but initializers generally aren't. This means that Page, which you are running a module_eval on, will only have the module_eval run once but will reload itself every request. config.to_prepare is a Rails hook that runs every single time providing great convenience for situations like this.

config/application.rb approach

class Application < Rails::Application
  # ... other stuff ...

  config.before_initialize do
    require 'page_extensions'
  end

  config.to_prepare do
    Page.send :include, Pants::Extensions::Page
  end
end

Engine approach

If you don't want to modify config/application.rb then you can, in Refinery CMS, create vendor/engines/add_page_extensions/lib/add_page_extensions.rb which would look like this:

require 'refinery'

module Refinery
  module AddPageExtensions
    class Engine < Rails::Engine

      config.before_initialize do
        require 'page_extensions'
      end

      config.to_prepare do
        Page.send :include, Pants::Extensions::Page
      end

    end
  end
end

If you use the engines approach you will also need to create vendor/engines/add_page_extensions/add_page_extensions.gemspec which should contain a simple gemspec:

Gem::Specification.new do |s|
  s.name = 'add_page_extensions'
  s.require_paths = %w(lib)
  s.version = 1.0
  s.files = Dir["lib/**/*"]
end

And then in your Gemfile add this line:

gem 'add_page_extensions', :path => 'vendor/engines'

If you go the engine approach, you will probably want to put all of your logic inside the engine's lib directory including the Pants::Extensions::Page code.

Hope this helps

这篇关于通过外部宝石增强模型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆