足場ジェネレーターの作成に関する問題
概要
Devise のスキャフォールディング ジェネレーターを構築しようとしていますが、source_root=File.expand_path(“../../templates/controllers”, FILE) 行のコメントを解除するとテストに合格するのに問題があります。 コントローラーのアクションで、次のエラーが発生します。
Could not find "confirmations_controller.rb" in any of your source paths. Your current source paths are:
/home/jackparsons210/devise/lib/generators/active_record/templates
.system temporary path is world-writable: /tmp
/tmp is world-writable: /tmp
[repeated thrice]
ただし、source_root を 2 回設定することはできません。これを克服する方法はありますか、それとも File コマンドを使用して独自のテンプレートをハッキングする必要がありますか?
# frozen_string_literal: true
require 'rails/generators/active_record'
require 'generators/devise/orm_helpers'
require "generators/devise/controllers_generator.rb"
module ActiveRecord
module Generators
class ScaffoldGenerator < ActiveRecord::Generators::Base
argument :attributes, type: :array, default: [], banner: "field:type field:type"
class_option :primary_key_type, type: :string, desc: "The type for primary key"
include Devise::Generators::OrmHelpers
source_root File.expand_path("../templates", __FILE__)
def copy_devise_migration
if (behavior == :invoke && model_exists?) || (behavior == :revoke && migration_exists?(table_name))
migration_template "migration_existing.rb", "#{migration_path}/add_devise_to_#{table_name}.rb", migration_version: migration_version
else
migration_template "migration.rb", "#{migration_path}/devise_create_#{table_name}.rb", migration_version: migration_version
end
end
def generate_model
invoke "active_record:model", [name], migration: false unless model_exists? && behavior == :invoke
end
def inject_devise_content
content = model_contents
class_path = if namespaced?
class_name.to_s.split("::")
else
[class_name]
end
indent_depth = class_path.size - 1
content = content.split("\n").map { |line| " " * indent_depth + line } .join("\n") << "\n"
inject_into_class(model_path, class_path.last, content) if model_exists?
end
def migration_data
<<RUBY
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
# t.integer :sign_in_count, default: 0, null: false
# t.datetime :current_sign_in_at
# t.datetime :last_sign_in_at
# t.#{ip_column} :current_sign_in_ip
# t.#{ip_column} :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
RUBY
end
def ip_column
# Padded with spaces so it aligns nicely with the rest of the columns.
"%-8s" % (inet? ? "inet" : "string")
end
def inet?
postgresql?
end
def rails5_and_up?
Rails::VERSION::MAJOR >= 5
end
def rails61_and_up?
Rails::VERSION::MAJOR > 6 || (Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR >= 1)
end
def postgresql?
ar_config && ar_config['adapter'] == 'postgresql'
end
def ar_config
if ActiveRecord::Base.configurations.respond_to?(:configs_for)
if rails61_and_up?
ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: "primary").configuration_hash
else
ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: "primary").config
end
else
ActiveRecord::Base.configurations[Rails.env]
end
end
def migration_version
if rails5_and_up?
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
end
end
def primary_key_type
primary_key_string if rails5_and_up?
end
def primary_key_string
key_string = options[:primary_key_type]
", id: :#{key_string}" if key_string
end
CONTROLLERS = %w(confirmations passwords registrations sessions unlocks omniauth_callbacks).freeze
puts File.expand_path("../../templates/controllers", __FILE__)
def create_controllers
#@_source_root=File.expand_path("../../templates/controllers", __FILE__)
@scope_prefix = table_name.blank? ? '' : ( table_name.camelize + '::')
controllers = CONTROLLERS
controllers.each do |name|
template "#{name}_controller.rb",
"app/controllers/#{table_name}/#{name}_controller.rb"
end
end
end
end
end
source_root が thor の初期化子に設定されているようです
def initialize(args = [], options = {}, config = {})
self.behavior = case config[:behavior].to_s
when "force", "skip"
_cleanup_options_and_set(options, config[:behavior])
:invoke
when "revoke"
:revoke
else
:invoke
end
super
self.destination_root = config[:destination_root]
end
解決策
すべてを 1 つのファイルにコピーする必要はありません。それは非常に逆効果です。
bin/rails g generator auth
# lib/generators/auth/auth_generator.rb
class AuthGenerator < Rails::Generators::NamedBase
# To override templates use:
#
# lib/templates/devise
# lib/templates/devise/controllers
#
# For example, to override one of the controller templates, create:
#
# lib/templates/devise/controllers/confirmations_controller.rb
#
def setup_devise
# Whatever you pass as arguments will be shared with invoked generators
invoke "devise"
# Because command arguments are shared, you have to make sure that
# each generator receives correct input. Here, the name has to
# be plural: User -> users
invoke "devise:controllers", [plural_name]
end
end
# lib/templates/devise/controllers/confirmations_controller.rb
# TODO: make <%= scope %> controller
追加のモデル属性をデバイス ジェネレーターに渡すことができます。
$ bin/rails g auth User bio:text -c confirmations
...
$ cat app/controllers/users/confirmations_controller.rb
# TODO: make users controller
Device:controllers は NamedBase ではなく Base ジェネレーターから継承しているため、ヘルパーは取得できません。これを回避する方法はわかりませんが、テンプレートを数回呼び出すだけです。
class AuthGenerator < Rails::Generators::NamedBase
def setup_devise
invoke "devise"
end
include Rails::Generators::ResourceHelpers
source_root File.expand_path("templates", __dir__)
CONTROLLERS = %w[confirmations passwords registrations sessions unlocks omniauth_callbacks].freeze
class_option :controllers, aliases: "-c", type: :array, desc: "Select specific controllers to generate (#{CONTROLLERS.join(", ")})"
def create_controllers
@scope_prefix = "#{controller_class_name}::"
controllers = options[:controllers] || CONTROLLERS
controllers.each do |c|
template "#{c}_controller.rb", "app/controllers/#{plural_name}/#{c}_controller.rb"
end
end
end
ここで、lib/generators/auth/templates にあるテンプレートを指す source_root が必要になります。
# lib/generators/auth/templates/confirmations_controller.rb
class <%= @scope_prefix %>ConfirmationsController < Devise::ConfirmationsController
end
ジェネレーターを作成したので、他の場所でも使用できます。
$ bin/rails g generator lazy
# lib/generators/lazy/lazy_generator.rb
class LazyGenerator < Rails::Generators::Base
def just_set_it_up
invoke "auth", ["User", "bio:text"], {controllers: ["confirmations"]}
end
end