Rails Gem: Running All Generators for given Namespace - ruby-on-rails

I'm developing a gem core with multiple sub modules, each it's own gem. As a developer, you'll be able to install the core and any other of the gems. How can I create a rake task or generator to run the generators for ALL of the installed gems with generators under the main gem namespace.
Example, if I my gem is called admin:
module Admin
module Generators
class InstallGenerator < Rails::Generators::Base
end
end
end
And I have another generator for one of the sub-gems:
module Admin
module Generators
class PostsGenerator < Rails::Generators::Base
end
end
end
And another one:
module Admin
module Generators
class TagslGenerator < Rails::Generators::Base
end
end
end
And there might be up to 10 more gems that can be installed. Rather than rail g admin:... installing each one, I would like to create a rake task or generator that runs all of the tasks.
Thanks in advance!

Keep an "AllGenerator" class under Admin module. The generator will have to do the following :
For each class under the namespace that is a generator class,
get the namespace from classname.
Call the invoke method with the namespace.
Something like this :
module Admin
module Generators
class AllGenerator < Rails::Generators::Base
def generator
Rails::Generators.lookup!
Admin::Generators.constants.each do |const|
generator_class = Admin::Generators.const_get(const)
next if self.class == generator_class
if generator_class < Rails::Generators::Base
namespace = generator_klass_to_namespace(generator_class)
invoke(namespace)
end
end
end
private
def generator_klass_to_namespace(klass)
namespace = Thor::Util.namespace_from_thor_class(klass)
return namespace.sub(/_generator$/, '').sub(/:generators:/, ':')
end
end
end
end
Here's the link to the gist with complete tested code
This way, running rails g admin:all would run every other generator directly under Admin::Generators .

First check out the following question and answer.
Find classes available in a Module
So all you have to do is access
Admin::Generators.constants.each do |c|
c = Admin::Generators.const_get(c)
if c < Rails::Generators::Base
c.new.run(your_args)
end
end
Only thing is I have never invoked a generator like this so it might be a little bit more then c.new.run, but I think that should do it.

Related

How to access rails methods in a rails plugin

I followed this tutorial to create a rails plugin.
Section 3 explains how to add a method to string that is available anywhere in a Rails application. Now for my case, I want to be able to access rails methods in the plugin itself.
For example in lib/plugin/my_plugin/my_plugin.rb I want to use:
Rails.application.eager_load!
But every time running the gem with an executable it throws following error:
undefined method `eager_load!' for nil:NilClass (NoMethodError)
Now I know this error is thrown because there's no Rails application, but I added the plugin to a Rails application and it also doesn't seem to work (same error) and can't find another way to get this working. Am I approaching this problem the wrong way or is there even a better way?
lib/plugin/my_plugin/my_plugin.rb:
require 'rails'
class MyPlugin
def fetch_models
arr_models = []
Rails.application.eager_load!
::ApplicationRecord.descendants.each do |model|
arr_models[] << model
end
end
end
Steps reproduce:
Create a new plugin rails plugin new custom
Add lib/custom/generator.rb
lib/custom/generator.rb:
require 'faker'
require 'rails'
class Generator
def fetch_models
arr_models = []
Rails.application.eager_load!
ApplicationRecord.descendants.each do |model|
arr_models[] << model
end
end
end
Edit lib/custom.rb
lib/custom.rb:
require "custom/generator"
require "custom/version"
require "custom/railtie"
module Custom
def self.generate
generator = Generator.new
models = generator.fetch_models
end
end
Add executable in bin/
bin/custom:
#!/usr/bin/env ruby
require 'custom'
Custom.generate
Edit custom.gemspec so that bin/test passes
Execute following commands:
$ bundle exec rake build
$ bundle exec rake install
Plugins usually are built to extend core classes.
Using the generator and following the linked guide at my_plugin/lib/my_plugin/rails_core_ext.rb you have to use class you want to extend.
Your code become
class RailsPlayground::Application
def fetch_models
arr_models = []
Rails.application.eager_load!
ApplicationRecord.descendants.each do |model|
arr_models[] << model
end
end
end
or
class RailsPlayground::Application
def fetch_models
Rails.application.eager_load!
ApplicationRecord.descendants
end
end

How to include a module in a model when Rails application is started?

How to do that? I tried this way inside my Engine:
config.to_prepare do
ActiveSupport.on_load :active_model do
include AAnyModule
end
end
But didn't work.
For including a module in a model, you can just add the include statement in the model itself. That will automatically load the module when you start the rails console.
Try this:
class A < ActiveRecord::Base
include AAnyModule
end

Rails - proper way to create admin section (`module` or `Admin::`)?

I'm new in Ruby and RoR and I'd like to create an admin section for a demo app.
From a little research I've done I've found two different options for creating an admin section. Example:
# config/routes.rb
namespace :admin do
resources :users
end
# app/controllers/admin_controller.rb
class AdminController < ApplicationController
before_filter :authorized?
...
end
Which of the two options is the most proper way to define controllers for the admin section, or they are both equally same?
# app/controllers/admin/users_controller.rb
# I think this is what rails generates if I run the "rails g controller admin::users" command
class Admin::UsersController < AdminController
...
end
# or instead
module Admin
class UsersController < AdminController
....
end
end
Both approaches yield to the same result, which is an UsersController which inherits from AdminController and is found in the Admin module (namespace).
Admin::MyClass is just a shortcut for module Admin ... class MyClass, but...
I would however prefer the explicit nested code (with module Admin on its own line), because it does make a different if the Admin-module has never been defined before. This probably won't happen to you when hacking with standard rails, but can happen when you write ruby code outside of rails.
See these examples:
class I::Am::AClass
end
i = I::Am::AClass.new
puts i.inspect
will lead to
i.rb:1:in `<main>': uninitialized constant I (NameError)
if you never declared the I and nested Am modules before in your code.
Whereas
module I
module Am
class AClass
end
end
end
i = I::Am::AClass.new
puts i.inspect
will work:
#<I::Am::AClass:0x00000001d79898>
because the modules are created along the path to AClass (at least this is how I think about it).
If you ever run in that problem and want to save whitespaces (because you will usually indent stuff in a module definition), there are some idioms to use. The one that solves the problem in the most obvious way (again, to me) is the following:
# Predefine modules
module I ; module Am ; end ; end
# Just a shortcut for:
# module I
# module Am
# end
# end
class I::Am::AClass
end
i = I::Am::AClass.new
puts i.inspect
#<I::Am::AClass:0x000000024194a0>
Just found that the nature of your question (it is not about an Admin-Interface, more about Module-Syntax) is also nicely discussed here Ruby (and Rails) nested module syntax . And I would love to see a ruby-bug report/feature-request on this :)
You can also use the administration framework for Ruby on Rails applications like
ActiveAdmin https://github.com/activeadmin/activeadmin
OR
Railsadmin
https://github.com/sferik/rails_admin

Reuse methods in a Rails generator

I'm writing a series of Rails generators that will share several of the same methods. I would like to abstract these methods into a module or class of their own to be reused (but not automatically fired) within each of my generators.
My latest attempt was to autoload a helper file and later include it:
lib/my_gem/engine.rb
module MyGem
class Engine < Rails::Engine
config.autoload_paths += Dir["#{config.root}/lib/helpers/**"]
end
end
lib/helpers/generators_helper.rb
module MyGem
module GeneratorsHelper
def some_method
# ...
end
end
end
lib/generators/my_gem/my_generator.rb
# ...
include MyGem::GeneratorsHelper
# ...
But I'll see something like Error: uninitialized constant MyGem::GeneratorsHelper.
I was able to accomplish this by manually requiring the file and then including the module. It's a little ugly, but keeps me from duplicating helper methods:
lib/my_gem/generators/my_generator.rb
require "#{Gem::Specification.find_by_name("my_gem").gem_dir}/lib/helpers/generators_helper.rb"
include MyGem::GeneratorsHelper

Add new migrations from Rails engine gem to app via generator

I'm building a Rails engine in a ruby gem. It includes some migrations right now that are called when you run:
rails g myengine:install
The code in the generator is as follows:
module MyEngine
module Generators
class InstallGenerator < ::Rails::Generators::Base
include Rails::Generators::Migration
source_root File.expand_path('../templates', __FILE__)
# ...
def copy_migrations
migration_template "migrations/migration1.rb", "db/migrate/migration1.rb"
migration_template "migrations/migration2.rb", "db/migrate/migration2.rb"
end
# ...
end
end
end
However, if I run rails g myengine:install again, it fails with this error:
Another migration is already named migration1: /Users/jh/Code/Web/demoapp/db/migrate/20130327222221_migration1.rb
I want it to just silently ignore the fact that there's already a migration and continue on to the next migration. What would be the best way to do this?
EDIT:
Per Dmitry's answer, this was my solution:
def copy_migrations
copy_migration "migration1"
copy_migration "migration2"
end
protected
def copy_migration(filename)
if self.class.migration_exists?("db/migrate", "#{filename}")
say_status("skipped", "Migration #{filename}.rb already exists")
else
migration_template "migrations/#{filename}.rb", "db/migrate/#{filename}.rb"
end
end
Using migration_template in Rails as example, you could perhaps check for destination = self.class.migration_exists?(migration_dir, #migration_file_name) and if migration already exists, skip over making migration_template call.

Resources