I am writing a Rails 3.2 generator and would like to use Thor::Shell::Basic instance methods (e.g. ask or yes?) like they do in the official Rails guides on Application Templates.
module MyNamespace
class ScaffoldGenerator < Rails::Generators::Base
source_root File.expand_path('../templates', __FILE__)
if yes? "Install MyGem?"
gem 'my_gem'
end
run 'bundle install'
end
end
This will give me a NoMethodError: undefined method 'yes?' for MyNamespace::ScaffoldGenerator:Class.
I cannot figure out a clean way to make those instance methods available - I am already inheriting from Rails::Generators::Base.
Edit:
Ah, it probably has nothing to do with Thor... I get a warning:
[WARNING] Could not load generator "generators/my_namespace/scaffold/scaffold_generator"
Something is not set up correctly although I used the generator for generating generators...
Oh, yes, it does have to do something with Thor.
Do not let yourself get confused by the warning. You know that Rails::Generators uses Thor, so walk over to the Thor Wiki and check out how Thor tasks work.
The rails generator execution will call any method in your generator. So make sure that you organize your stuff into methods:
module MyNamespace
class ScaffoldGenerator < Rails::Generators::Base
source_root File.expand_path('../templates', __FILE__)
def install_my_gem
if yes? "Install MyGem?"
gem 'my_gem'
end
end
def bundle
run 'bundle install'
end
end
end
Be sure to put your generator into the right folder structure, e.g. lib/generators/my_namespace/scaffold_generator.rb.
Thanks for asking your question, dude!
Related
As a personal project, I decided to write a minified-version of Ruby on Rails and upload it as a gem using Bundle called railz_lite.
Inside of my project, I was hoping to implement a Generator similar to rails new, which would create the necessary folders for a web app i.e. controllers/, views/, models/, etc.
To do so, I included Thor as a dependency, then created the following files:
require 'thor/group'
module RailzLite
module Generators
class Project < Thor::Group
include Thor::Actions
def self.source_root
File.dirname(__FILE__) + "/templates"
end
def add_controllers
empty_directory("controllers")
end
def add_models
empty_directory("models")
end
def add_server
template("server.rb", "config/server.rb")
end
def add_views
empty_directory("views")
end
def add_public
empty_directory("public")
end
end
end
end
Inside the gem project's root folder, when I run bundle exec railz_lite new, the generator works just fine and the necessary files are created.
However, if I create a new project, puts my gem (railz_lite) in the Gemfile, run bundle install, then execute bundle exec rails_lite new, I am greeted with the following error:
.rbenv/versions/2.5.1/lib/ruby/2.5.0/fileutils.rb:232:in `mkdir':
: Read-only file system # dir_s_mkdir - /controllers (Errno::EROFS)
I suspect the error is because the empty_directory command is not referring to the root directory of the project I just created. I am hoping that there is a simple way to fix this.
For further reference, the CLI script and class look as follows:
railz_lite
#!/usr/bin/env ruby
require 'railz_lite/cli'
RailzLite::CLI.start
cli.rb
require 'thor'
require 'railz_lite'
require 'railz_lite/generators/project'
module RailzLite
class CLI < Thor
desc 'new', 'Generates a new RailzLite project'
def new
RailzLite::Generators::Project.start([])
end
end
end
Any solutions would be greatly appreciated!
Note: I am running this on macOS Catalina.
So I found a solution after searching extensively through gem forums and looking at the Rails source code.
Inside of the generation I have to manually set the destination_root to the working directory of the project. The working directory can be found with Dir.pwd
require 'thor/group'
module RailzLite
module Generators
class Project < Thor::Group
include Thor::Actions
def self.source_root
File.dirname(__FILE__) + "/templates"
end
def self.destination_root # this method fixes the problem!
Dir.pwd # get the current project directory
end
def add_controllers
empty_directory("controllers")
end
def add_models
empty_directory("models")
end
def add_server
template("server.rb", "config/server.rb")
end
def add_views
empty_directory("views")
end
def add_public
empty_directory("public")
end
end
end
end
My cop:
# lib/rubocop/cop/myproject/my_cop.rb
require 'rubocop'
module RuboCop
module Cop
module MyProject
class MyCop < RuboCop::Cop::Cop
# ...
end
end
end
end
This cop needs to know some global settings Rails. For example, Rails.logger.log_level
But I get errors:
1) undefined method 'logger' for RuboCop::Cop::Rails:Module - when I call Rails.logger.log_level
2) uninitialized constant Rails - when I call ::Rails.logger.log_level
Can this be done or is it a stupid idea?
As an option you can do:
# lib/rubocop/cop/myproject/my_cop.rb
require 'rubocop'
require_relative '../../../../../config/environment'
module RuboCop
module Cop
module MyProject
class MyCop < RuboCop::Cop::Cop
# ...
end
end
end
end
And call ::Rails.logger.level
Rubocop is a static code analyzer. Which means when you run rubocop command, it does not load any ruby environments, including Rails. It just reads ruby files and analyses those as text files.
So the short answer is: no, it can not be achieved with Rubocop.
I know how to create a rails generator gem which is called like:
rails g my_generator command
And I know how to create a thor gem which can be called like:
my_generator command
But I don't know how to create a rails generator that can be called using an executable. I have tried by creating a lib/my_generator/cli.rb file like:
require 'thor'
module Mang
class Cli < Thor
include ::Rails::Generators::Base
desc "install_gem", "install a gem"
def install_gem
gem 'thor', "0.18.1"
end
end
end
But I get the following error despite having added Rails as a dependency in my gemspec.
uninitialized constant Rails (NameError)
The fix was just a matter of including the rails/generators/actions module.
require 'thor'
require 'rails/generators'
require 'rails/generators/actions'
module Mang
class Cli < Thor
include Thor::Actions
include Rails::Generators::Actions
desc "install_gem", "install a gem"
def install_gem
gem 'thor', "0.18.1"
end
end
end
Might sound like a simple question, but I'm stumped.
I've created a gem that essentially contains a generator.
It contains the following structure:
lib
- generators
- my_generator
my_generator_generator.rb (see below)
- templates
my_template_files...
- my_generator.rb (empty file)
test
-test files
GemFile
etc..
However when I add this Gem to my gem file and run rails g, it's not listed. Is there any additional config that I need to do?
My generator roughly looks like this...
class MyGeneratorGenerator < Rails::Generators::NamedBase
source_root File.expand_path('../templates', __FILE__)
generator code....
end
The strange thing is, it works in Cygwin, but not in Ubuntu...
This took a little bit for me to figure out, but I've run into the same problem. Here is how I fixed it.
Tree structure looks like this:
lib
- generators
- gemname
install_generator.rb
- templates
(template files)
Here's the code for install_generator.rb
#lib/generators/gemname/install_generator.rb
require 'rails/generators'
module Gemname
class InstallGenerator < Rails::Generators::Base
desc "Some description of my generator here"
# Commandline options can be defined here using Thor-like options:
class_option :my_opt, :type => :boolean, :default => false, :desc => "My Option"
# I can later access that option using:
# options[:my_opt]
def self.source_root
#source_root ||= File.join(File.dirname(__FILE__), 'templates')
end
# Generator Code. Remember this is just suped-up Thor so methods are executed in order
end
end
When I run
rails g
I see:
Gemname
gemname:install
Some other things you may need to setup:
#lib/gemname.rb
module Gemname
require 'gemname/engine' if defined?(Rails)
# any additional requires
end
and
#/lib/gemname/engine.rb
require 'rails'
module Gemname
class Engine < Rails::Engine
end
end
Some good references I've found on this are:
http://textmate.rubyforge.org/thor/Thor.html (take a look at the modules, especially Thor::Actions)
http://api.rubyonrails.org/classes/Rails/Generators/Base.html
http://api.rubyonrails.org/classes/Rails/Generators/Actions.html
https://github.com/wycats/thor/blob/master/README.md
http://www.themodestrubyist.com/2010/03/16/rails-3-plugins---part-3---rake-tasks-generators-initializers-oh-my/
If you use Railtie, you can define your generator wherever it could be using:
generators do
require "path/to/my_railtie_generator"
end
in Railtie class.
I have a problem creating a Rails plugin, lets call it Mplug. The plugin is pretty much only a rake task, but with a library that the rake task uses.
The problem is to require files. Lets say that this is the rake task:
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
This will not recognize the constant Mplug. So I thought I needed to require it.
require 'mplug'
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
But then I get this message.
no such file to load -- mplug
So, ok. Lets try to give the path to the plugin then.
require 'vendor/plugins/mplug/lib/mplug'
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
This actually works. However, except that I guess that this is a bad way to do it, I now have to require the files in my plugin as if I was in the rails root. For example:
module Mplug
end
require 'mplug/indexer'
Now has to be:
module Mplug
end
require 'vendor/plugins/mplug/lib/mplug/indexer'
Which I do not want to do of course.
Is there any neat way to solve this?
Thanks!
The easiest solution to this problem would be to register the rake task using the Rails::Railtie API. In lib/mplug.rb, define your Railtie:
module Mplug
class Railtie < ::Rails::Railtie
rake_tasks do
load "mplug/rails.rake"
end
end
end
Then, in lib/mplug/rails.rake:
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
Then, make sure your plugin is defined in your Gemfile. If your plugin is in vendor/plugins, add this line to your Gemfile:
gem "mplug", :path => "vendor/plugins/mplug"
If you push the plugin to a git repo, use :git.
Now, rake mplug:create will be available! If you want it to show up in rake -T, make sure you add a description:
namespace :mplug do
desc "creating an mplug"
task :create do
Mplug::Indexer.new
end
end
One option is to use the FILE constant, and then provide the rest of the path relative to the current file:
require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'mplug')
(if your rake task file is in your plugin_root/tasks...)