开发者

Modify a file with a rails generator

How do you make a generator that alters a file.

Im trying to make it so that it finds a pattern in a file and adds come con开发者_开发知识库tent to the line below it.


Rails' scaffold generator does this when it adds a route to config/routes.rb It does this by calling a very simple method:

def gsub_file(relative_destination, regexp, *args, &block)
  path = destination_path(relative_destination)
  content = File.read(path).gsub(regexp, *args, &block)
  File.open(path, 'wb') { |file| file.write(content) }
end

What it's doing is taking a path/file as the first argument, followed by a regexp pattern, gsub arguments, and the block. This is a protected method that you'll have to recreate in order to use. I'm not sure if destination_path is something you'll have access to, so you'll probably want to pass in the exact path and skip any conversion.

To use gsub_file, let's say you want to add tags to your user model. Here's how you would do it:

line = "class User < ActiveRecord::Base"
gsub_file 'app/models/user.rb', /(#{Regexp.escape(line)})/mi do |match|
  "#{match}\n  has_many :tags\n"
end

You're finding the specific line in the file, the class opener, and adding your has_many line right underneath.

Beware though, because this is the most brittle way to add content, which is why routing is one of the only places that uses it. The example above would normally be handled with a mix-in.


I like Jaime's answer. But, when I started to utilize it, I realized I needed to make some modifications. Here is the sample code I am using:

private

  def destination_path(path)
    File.join(destination_root, path)
  end

  def sub_file(relative_file, search_text, replace_text)
    path = destination_path(relative_file)
    file_content = File.read(path)

    unless file_content.include? replace_text
      content = file_content.sub(/(#{Regexp.escape(search_text)})/mi, replace_text)
      File.open(path, 'wb') { |file| file.write(content) }
    end

  end

First, gsub will replace ALL instances of the search text; I only need one. Ther0fore I used sub instead.

Next, I needed to check if the replacement string was already in place. Otherwise, I'd be repeating the insert if my rails generator was ran multiple times. So I wrapped the code in an unless block.

Finally, I added the def destination_path() for you.

Now, how would you use this in a rails generator? Here is an example of how I make sure simplecov is installed for rspec and cucumber:

  def configure_simplecov
    code = "#Simple Coverage\nrequire 'simplecov'\nSimpleCov.start"

    sub_file 'spec/spec_helper.rb', search = "ENV[\"RAILS_ENV\"] ||= 'test'", "#{search}\n\n#{code}\n"
    sub_file 'features/support/env.rb', search = "require 'cucumber/rails'", "#{search}\n\n#{code}\n"
  end

There is probably a more elegant and DRY-er way to do this. I really liked how you can add a block of text the Jamie's example. Hopefully my example adds a bit more functionality and error-checking.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜