簡體   English   中英

用Ruby替換Ruby源文件中的函數

[英]Replacing a function in a Ruby source file with Ruby

首先,我將Ruby 2.2.0與Rails 4.2.0結合使用(因此,如果其中有任何幫助,我將安裝ActiveSupport)。

什么

我想要做的是讀取一個Ruby文件並采用其中聲明的任何方法,然后將它們寫入/覆蓋到另一個可能已經聲明了這些方法的Ruby文件中。

我想在源文件中執行此操作,它應作為一種預處理程序完成,而不是在運行時完成。 我不是在談論開放類 ,而是要永久編輯源文件。

如果什么也沒有,那么我將做一些事情來讀取文件,逐行查找defend語句,但是我想知道是否已經存在這樣的東西?

為什么

部分地與我一起工作,部分地看是否可以,我在Ruby中編寫了一個腳本,該腳本使用繪圖中的一些進一步的約束來解析由ArgoXML生成的XMI文件,以便我的腳本知道何時使用has_manyhas_many :through ,后來我用了所有的精力。

然后,它將在我的所有模型等的外殼腳本中創建Rails生成器命令,並使用awk注入需要手動設置的任何關系(因為與生成器不符)。

這很快為大約100個表創建了模型和數據庫條目。 顯然,盡管再次運行我的腳本會刪除所有現有文件(或問我要執行的操作100倍,考慮到大部分時間都在銷毀,我寧願不這樣做)。

例:

我很高興有一個帶有函數聲明的類,或者可以將其放在命名的類或模塊中,這沒關系。 文件中將永遠只有一個類。

我要發生的是讀取文件A的內容,從文件中獲取每種方法。 然后,我想將這些方法覆蓋到文件B的源中,以便獲得文件C。

文件A(其方法需要插入文件B的文件)

def show
  puts params[:rule_set_edition_id]
  @description = @rule.description
  @input_lines = @description.split("\\n")
  @output_lines = []
  for input_line in @input_lines do
    if input_line != "" then
      @output_lines << input_line
    end
  end

  @rule_set_edition 
end

def some_other_method
  puts "FooBar"
end

文件B(零件-標准Rails支架,覆蓋之前)

class RulesController < ApplicationController
  before_action :set_rule, only: [:show, :edit, :update, :destroy]

  # GET /rules
  # GET /rules.json
  def index
    @rules = Rule.all
  end

  # GET /rules/1
  # GET /rules/1.json
  def show
  end

  # GET /rules/new
  def new
    @rule = Rule.new
  end

  # GET /rules/1/edit
  def edit
  end

  . . . etc

end

文件C(部分-我想要的輸出文件)

class RulesController < ApplicationController
  before_action :set_rule, only: [:show, :edit, :update, :destroy]

  # GET /rules
  # GET /rules.json
  def index
    @rules = Rule.all
  end

  # GET /rules/1
  # GET /rules/1.json
  def show
    puts params[:rule_set_edition_id]
    @description = @rule.description
    @input_lines = @description.split("\\n")
    @output_lines = []
    for input_line in @input_lines do
      if input_line != "" then
        @output_lines << input_line
      end
    end

    @rule_set_edition 
  end

  def some_other_method
    puts "FooBar"
  end

  # GET /rules/new
  def new
    @rule = Rule.new
  end

  # GET /rules/1/edit
  def edit
  end
end

更新說明 :我意識到這是錯誤的atm,正在對其進行修復。

正如我在XMI Parser類中已經有了一個@models變量,它以簡潔的格式包含了XMI上的所有信息,我可以向解析器中添加一個函數來破解模板。

我使用了@ChrisHeald提供的代碼,將其放在名為hack_templates.rb的文件中,並使其接受命令行ARGV作為實現和模板文件的位置。

然后,我的新函數在輸出中添加了額外的shell命令行。

    def hack_templates
      hacks = []
      for idref, model in @models do
        # Controllers
        controller_name = "#{model['name'].tableize}_controller.rb"
        controller_implementation = "./db/init/controllers/#{controller_name}"
        countroller_template = "./app/controllers/#{controller_name}"
        if File.exist?(countroller_template) and File.exist?(controller_implementation) then
          puts "ruby hack_template.rb #{controller_implementation} #{countroller_template}"
        end
        # Models

        # Views
      end
      hacks.join("\n")
    end

還有@ChrisHeald代碼中的hack_templates.rb:

require 'method_source'
include MethodSource::CodeHelpers

$implementation = open(ARGV[0])
$template = open(ARGV[1])

module Implementation
  class << self
    eval $implementation.read
  end
end

@template_by_line = $template.each_line.to_a

(Implementation.methods - Module.new.methods).each do |method|
  $implementation.rewind
  # Get the source code of our implementation method
  impl_source = expression_at $implementation, Implementation.method(method).source_location[1]

  # Get the line that this method starts on in the template
  template_line = @template_by_line.index {|line| line.match(/^\s*def #{method}/) }

  if template_line
    # If we found a match, replace it
    # Get the source code for the template method
    tmpl_source = expression_at @template_by_line, template_line + 1

    # Replace it with the implemetation method
    @template_by_line[template_line] = impl_source

    # Remove any extra lines from the template method
    tmpl_source.split(/\n/).length.times do |len|
      @template_by_line.delete_at template_line + len + 1
    end
  else
    # find the last `end` in the template array and insert the implementation source before it
    last_end = @template_by_line.rindex {|line| line.match(/^\s*end/)}
    @template_by_line.insert(last_end, "\n" + impl_source + "\n")
  end

end

File.open($template, "w+") do |f|
   f.syswrite(@template_by_line.join)
end

這將僅從第二次運行開始起作用,因為除非存在舊代碼,否則在我稱為該代碼的位置控制器將不存在。 對我來說,只運行兩次可能很容易,但我也可以將hack_template.rb命令發送到與主output.sh不同的文件中

進一步的更新最后,我最終使用了Rails Concerns。 我遵循ClassNameConcern的命名約定為每個類創建新的關注點。 然后,我把所有的類方法都放在了一個問題上,使我的模型只剩下自動生成的東西。

由於我喜歡100個模型,到目前為止我已經編輯了4個模型,因此效果很好。 @ChrisHeald的答案仍然是我的問題的正確答案。

發電機

最簡單的方法是使用自定義生成器模板。

在Rails 3.0及更高版本中,生成器不僅在源根目錄中查找模板,而且還在其他路徑中搜索模板。 其中之一是lib / templates。 由於我們要自定義Rails :: Generators :: HelperGenerator,因此我們可以通過在lib / templates / rails / helper中簡單創建一個名為helper.rb的模板副本來實現。

http://guides.rubyonrails.org/generators.html#customizing-your-workflow-by-changing-generators-templates

控制器的模板在這里:

https://github.com/rails/rails/blob/master/railties/lib/rails/generators/rails/controller/templates/controller.rb

因此,您只需在lib/templates/rails/controller/controller.rb創建一個副本,然后將其自定義為類似以下內容:

<% if namespaced? -%>
require_dependency "<%= namespaced_path %>/application_controller"
<% end -%>
<% module_namespacing do -%>
class <%= class_name %>Controller < ApplicationController

  def show
    puts params[:rule_set_edition_id]
    @description = @rule.description
    @input_lines = @description.split("\\n")
    @output_lines = []
    for input_line in @input_lines do
      if input_line != "" then
        @output_lines << input_line
      end
    end

    @rule_set_edition
  end

  def some_other_method
    puts "FooBar"
  end

<% (actions - [:show, :some_other_method]).each do |action| -%>
  def <%= action %>
  end
<%= "\n" unless action == actions.last -%>
<% end -%>
end
<% end -%>

然后,當您生成控制器時,Rails將使用您的模板(及其實現)。

就是說,如果這些方法在所有生成的控制器之間是通用的,則應考慮簡單地從一個通用的超類繼承您的控制器,然后使用show的默認實現如下所示:

def show
  super
end

還有一些選項可以使用source_location對生成的模板進行后處理,以檢測方法邊界,但這很麻煩。 目前,我正在為此進行概念驗證。

實際修改模板

我們將使用method_source gem,它使用Method#source_location查找方法的起始位置,然后對行進行求值,直到找到完整的表達式。 一旦有了,就可以用實現替換給定的方法。

這是非常糟糕的,如果可行,我建議不要這樣做。

implementation.rb

  def show
    puts params[:rule_set_edition_id]
    @description = @rule.description
    @input_lines = @description.split("\\n")
    @output_lines = []
    for input_line in @input_lines do
      if input_line != "" then
        @output_lines << input_line
      end
    end

    @rule_set_edition
  end

  def some_other_method
    puts "FooBar"
  end

template.rb

class RulesController < ApplicationController
  before_action :set_rule, only: [:show, :edit, :update, :destroy]

  # GET /rules
  # GET /rules.json
  def index
    @rules = Rule.all
  end

  # GET /rules/1
  # GET /rules/1.json
  def show
  end

  # GET /rules/new
  def new
    @rule = Rule.new
  end

  # GET /rules/1/edit
  def edit
  end

  # . . . etc
end

converter.rb

require 'method_source'
include MethodSource::CodeHelpers

$implementation = open("implementation.rb")
$template = open("template.rb")

module Implementation
  class << self
    eval $implementation.read
  end
end

@template_by_line = $template.each_line.to_a

(Implementation.methods - Module.new.methods).each do |method|
  $implementation.rewind
  # Get the source code of our implementation method
  impl_source = expression_at $implementation, Implementation.method(method).source_location[1]

  # Get the line that this method starts on in the template
  template_line = @template_by_line.index {|line| line.match(/^\s*def #{method}/) }

  if template_line
    # If we found a match, replace it
    # Get the source code for the template method
    tmpl_source = expression_at @template_by_line, template_line + 1

    # Replace it with the implemetation method
    @template_by_line[template_line] = impl_source

    # Remove any extra lines from the template method
    tmpl_source.split(/\n/).length.times do |len|
      @template_by_line.delete_at template_line + len + 1
    end
  else
    # find the last `end` in the template array and insert the implementation source before it
    last_end = @template_by_line.rindex {|line| line.match(/^\s*end/)}
    @template_by_line.insert(last_end, "\n" + impl_source + "\n")
  end

end
puts @template_by_line.join

和一些輸出:

$ ruby converter.rb
class RulesController < ApplicationController
  before_action :set_rule, only: [:show, :edit, :update, :destroy]

  # GET /rules
  # GET /rules.json
  def index
    @rules = Rule.all
  end

  # GET /rules/1
  # GET /rules/1.json
  def show
    puts params[:rule_set_edition_id]
    @description = @rule.description
    @input_lines = @description.split("\\n")
    @output_lines = []
    for input_line in @input_lines do
      if input_line != "" then
        @output_lines << input_line
      end
    end

    @rule_set_edition
  end

  def new
    @rule = Rule.new
  end

  # GET /rules/1/edit
  def edit

  def some_other_method
    puts "FooBar"
  end  end

  # . . . etc
end

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM