首页 > 解决方案 > 复制控制器类实例变量

问题描述

我想编写一个简单的 erb 模板生成器来使用Generator模块从视图中解析存储的 erb 模板。我Generator从 rails 控制器调用它来生成它的单例实例并通过指针传递WallControllerself

require 'generator'

class WallController < ApplicationController
  def index
    header = File.read 'app/views/application-header.html'.freeze
    @instances = {header: header}
    # Load view generators
    Generator.generate_instances self
  end 
end

Generator.generate_instances实际尝试做的第一件事是复制WallController实例变量(因此是自指针)以执行对 erb 模板的正确解析。然后它生成返回 erb 结果文本的方法。

require 'erb'

module Generator
  def self.generate_instances environment
    # Mimic class environment
    if environment.superclass == ApplicationController
      environment.instance_variables.each do |v| 
        puts "Copy instance variable '#{v}' from #{environment.name} to #{self.name}"
        value = environment.instance_variable_get(v)
        self.send :instance_variable_set, v, value
      end 
    end 
    # Parse the ERB templates
    templates = @instances
    return 0 if !templates.is_a?(Hash) or templates.empty? 
    templates.keys.each.with_index do |key, index|
      define_singleton_method key do
        ERB.new(templates.values[index]).result
      end
    end 
  end 
end

接口的使用Generator将如下所示:

<%=== Generator.header %>

我是 Rails 新手,但我发现 Rails 控制器包含的文件仅限于单个静态结构。我没有设法覆盖class Objectclass Class单例方法,这可能会有所帮助。

但是,在运行上面的示例之后,实例变量WallController返回WallController类地址而不是由定义的值WallController.index

undefined method `empty?' for #<WallController:0x000000000a1f90>

是否有正确的方法在其他控制器之间分配 rails 控制器实例变量?如果没有,为什么常规实例副本不起作用?

如果我必须用 ruby​​ 编写它,那将很容易:

module Y
  def self.generate_environment environment
    environment.instance_variables.each do |v| 
      puts "Copy #{v} from #{environment.name} to #{self.name}"
      value = environment.instance_variable_get v
      self.instance_variable_set(v, value)
    end if environment.class == Class
        
    puts "Retrived string: #{@hello}"
  end 
end

class X
  def self.index
    @hello = 'Hello, World!'
    Y.generate_environment self
  end 
end

X.index

标签: ruby-on-railsrubycontrollererb

解决方案


这个问题可以用viewcomponent解决,它允许视图控制器的标准 ruby​​ 代码。还解决了以合理的速度将视图代码划分为更小的可重用组件的问题。

要使用视图组件 gem,首先将它包含到您的 Gemfile 中。

gem 'view_component', require: 'view_component/engine'

使用 更新您的 gem 后bundle install,如果服务器正在运行,您还需要重新启动服务器,以应用新的 gem。

然后生成组件的用法与其他 Rails 生成器类似。第一个参数是组件名称,第二个是组件参数。

rails generate component Header site_id

现在我专注于在app/component目录、视图和控制器代码中生成的文件。这将只是创建视图标题片段的控制器。

里面app/component/header_component.rb可以封装所有WallController与header view相关的代码。

class HeaderComponent < ViewComponent::Base
  def initialize(site_id:)
    puts "Rendering header component for site: #{site_id}"
    # Load site elements
    @site = Site.find site_id
    @menu_items = []
    Site.all.each.with_index do |site, index|
      @menu_items.push site.title => site.link
    end 
  end
end

同样,把所有的header view erb代码放到app/component/header.html.erb.

可以使用 rails render 从视图中生成完成的组件:

  <%= render HeaderComponent.new(site_id: 1) %>

推荐阅读