Скопируйте переменные экземпляра класса контроллера

#ruby-on-rails #ruby #controller #erb

Вопрос:

Я хочу написать простой генератор шаблонов erb для анализа сохраненных шаблонов erb из представлений с помощью Generator модуля. Я вызываю контроллер Generator из rails, чтобы сгенерировать его одноэлементные экземпляры и передать ему указатель WallController by self .

 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 Object , ни class 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
 

Комментарии:

1. Вы передаете экземпляр WallController в Generator.generate_instances as environment , а затем проверяете, является ли этот экземпляр Class с environment == Class . Это environment == Class всегда приведет к сбою, поэтому вы в конечном итоге не будете копировать какие-либо переменные экземпляра.

2. Ой, спасибо тебе. Плохая опечатка. Я исправил это в вопросе сейчас.

3. Вы все еще передаете self , что является экземпляром, а затем проверяете , является ли env.class == Class это ложным, как self и WallController

4. WallController.class == class , это правда.

5. Однако я меняю его environment.superclass == ApplicationController на более конкретный.

Ответ №1:

Эта проблема может быть решена с помощью viewcomponent, который позволяет использовать стандартный код ruby для контроллера представления. Также решает проблему разделения кода представления на более мелкие повторно используемые компоненты с разумной скоростью.

Чтобы использовать драгоценный камень viewcomponent, сначала включите его в свой файл Gemfile.

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

После обновления ваших драгоценных bundle install камней вам также потребуется перезагрузить сервер , если он запущен, чтобы применить новый драгоценный камень.

Затем создание компонента аналогично использованию других генераторов rails. Первый аргумент — это имя компонента, а второй-аргумент компонента.

 rails generate component Header site_id
 

Теперь я сосредоточусь на файлах, созданных в app/component каталоге, представлении и коде контроллера. Это будет просто контроллер для создания фрагмента заголовка представления.

Внутри app/component/header_component.rb может быть инкапсулирован весь код, WallController относящийся к представлению заголовка.

 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
 

Аналогично, поместите весь erb-код представления заголовка в app/component/header.html.erb .

Готовый компонент может быть сгенерирован из представления с помощью рендеринга rails:

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