Add Migrations to a Rails Engine's Install Generator - ruby-on-rails

In my rails 5 engine I want to include the installation of the engine's migrations with the custom install generator I have at:
myengine/lib/generators/myengine/install_generator.rb
This generator currently looks like this:
module Myengine
module Generators
class InstallGenerator < Rails::Generators::Base
source_root File.expand_path("../../templates", __FILE__)
desc "Creates a Myengine initializer and copys template files to your application."
def copy_initializer
template "myengine.rb", "config/initializers/myengine.rb"
end
end
end
end
When I add the engine to a rails app, instead of having to call:
rails g myengine:install
then
rails myengine:install:migrations
How can I add the creation of those migrations to the custom generator?

Fun question! And one that I hope I can answer. Let's say you have an engine named "Buttafly" and a generator living at:
#lib/generators/buttafly/install/install_generator.rb
At the top of your generator, require the 'date' library like so:
require 'date'
Then in the body of your generator, add a method definition like:
def copy_migrations
# get an array of the migrations in your engine's db/migrate/
# folder:
migrations = Dir[Buttafly::Engine.root.join("db/migrate/*.rb")]
migrations.each_with_index do |migration, i|
# The migrations will be created with the same timestamp if you
# create them all at once. So if you have more than one migration
# in your engine, add one second to the second migration's file
# timestamp and a third second to the third migration's timestamp
# and so on:
seconds = (DateTime.now.strftime("%S").to_i + i).to_s
seconds = seconds.to_s.length == 2 ? seconds : "0#{seconds}"
timestamp = (DateTime.now.strftime "%Y%m%d%H%M") + seconds
# get the filename from the engine migration minus the timestamp:
name = migration.split("/").split("_").last
# See if a the name of your engine migration is already in your
# host app's db/migrate folder:
if Rails.root.join("db/migrate/*#{name}").exist?
# do nothing:
puts "Migration #{name} has already been copied to your app"
else
# copy your engine migration over to the host app with a new
# timestamp:
copy_file m, "db/migrate/#{timestamp}_buttafly_#{name}"
end
end
end

Related

Ruby Gem Project -- Thor Generator Causing Read-only File System Error

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

why does my rails generator template method results in 'file_clash'

So I'm writing a rails generator to do the most simple of things: copy some model files from a gem's lib/generators/pathways/templates directory into a project in the app/models directory. My possibly mistaken understanding is that the template method is basically a file copy with source/target.
(note that "pathways" is the name of my gem that installs this generator)
Here's the guts of the copy code in the generator:
def copy_models
project_models_location = "#{Rails.root}/app/models/"
[
"pathways_experiment.rb",
...
].each do |filename|
puts "copying #{filename} to #{project_models_location}"
template filename, "#{project_models_location}"
end
end
The puts displays what I expected:
copying pathways_experiment.rb to
/Users/meuser/Projects/testing_gem/exp_gem_test/app/models/
however, the call to the templates method dumps this output:
file_clash app/models
I checked the target directory and there are no files in it, so it doesn't seem to be because the code is trying to overwrite the file.
here's the source of the entire generator in case I simply haven't included or extended the correct classes/modules:
require 'rails/generators'
require 'rails/generators/active_record'
module Pathways
class InstallGenerator < ActiveRecord::Generators::Base
include Rails::Generators::Migration
source_root File.expand_path("../templates", __FILE__)
def code_that_runs
puts "PATHWAYS: installing models"
copy_models
end
private
def copy_models
project_models_location = "#{Rails.root}/app/models/"
[
"pathways_experiment.rb",
...
].each do |filename|
puts "copying #{filename} to #{project_models_location}"
template filename, "#{project_models_location}"
end
end
end
end

Rails 3 :: config/initializers separate directory per environment

What would be the best way to separate out initializers in a separate subdirectory per environment in Rails 3+?
I heard that all subdirectories of config/initializers will be picked up, so a subdirectory per environment there wouldn't work unless creatively monkey patched.
Example: config/.../initializers/<environment>/*.rb
This answer avoids complexity of getting a Rails::Engine working and is credited to Bosco of SF Ruby on Rails meetup and friends. It also permits overriding via the command-line / shell setup and works with Heroku.
0) Change config/initializers/**/*.rb that look this:
FB_SECRET_KEY = 'kjkjdsfkjalsfyoursecretherelkjsdfljkasdfljdjlf'
To:
FB_SECRET_KEY = ENV['APPNAME_FB_SECRET_KEY']
1) I created a config/secrets.rb which is in .gitignore like this:
ENV['APPNAME_FB_SECRET_KEY'] ||= 'kjkjdsfkjalsfyoursecretherelkjsdfljkasdfljdjlf'
. . .
2) Added this line to start of scripts/rails:
require File.expand_path('../../config/secrets', __FILE__)
3) And the addition to .gitignore
config/secrets.rb
Bonus) Add a config/secrets.rb.example template file which is not in .gitignore.
# config/application.rb
module MyProject
class Application < Rails::Application
# modifies initializers to load as follows:
#
# 1. config/initializers/*.rb
# 2. config/initializers/environments/#{Rails.env}/**/*.rb
#
unless config.paths['config/initializers_environment']
old_initializers_dir = config.paths['config/initializers']
config.paths['config/initializers'] = Dir[File.join(old_initializers_dir, '*.rb')]
config.paths['config/initializers_environment'] = Dir[File.join(old_initializers_dir, 'environments', Rails.env, '**', '*.rb')]
initializer :load_per_environment_initializers do
config.paths['config/initializers_environment'].to_a.sort.each do |initializer|
load(initializer)
end
end
end
# ...
https://gist.github.com/1338248

Rails 3 generators in gem

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.

Generating Missing Spec Files for RSpec

Is there any command available for generating all missing spec files for existing models / controllers? I have a project that has several models that have been generated with out spec files.
In rspec-rails-2 which is intended for Rails 3 all of the rspec generators have been removed.
You can solve this problem by running the rails model generator. You can add -s to skip any existing files and --migration=false to skip creating the migration file.
Like so:
rails generate model example -s --migration=false
You could just run the generator and ignore the models/migrations/fixtures.
ruby script/generate rspec_model User --skip-migration --skip-fixture --skip
I've been looking into writing something to do this but there hasn't been any interest from others.
If the number of missing specs is rather small, you could simply run the rails generate commands for each components with missing specs.
When a conflict arises, simply opt not to overwrite the original file. The generator will ignore the existing files and generate the missing ones.
https://gist.github.com/omenking/7774140
require 'fileutils'
namespace :spec do
def progress name, x, y
print "\r #{name}: #{x}/#{y} %6.2f%%" % [x.to_f/y * 100]
end
def generate_files name
kind = name.to_s.singularize
collection = Dir.glob Rails.root.join('app',name.to_s,'**','*').to_s
root = Rails.root.join('app',name.to_s).to_s<<'/'
ext = case name
when :controllers then '_controller.rb'
when :models then '.rb'
end
count = collection.count
collection.each_with_index do |i,index|
`rails g #{kind} #{$1} -s` if i =~ /#{root}(.+)#{ext}/
progress name, index, count
end
end
task generate_missing: :environment do
generate_files :controllers
generate_files :models
end
end
# if you dont want certian things generated than
# configure your generators in your application.rb eg.
#
# config.generators do |g|
# g.orm :active_record
# g.template_engine :haml
# g.stylesheets false
# g.javascripts false
# g.test_framework :rspec,
# fixture: false,
# fixture_replacement: nil
# end
#

Resources