How to get generators call other generators in rails 3 - ruby-on-rails

I am experimenting with gem development, right now specifically generators. So far I have successfully created two generators that do their job just perfectly. These two generators are in the same directory.
However, right now I have to call each of them separately.
What I'd like to do is just call one generator and have that generator call all the other ones. Just would type
rails g generator_name
and this would call x other generators.
Does anyone know how would I got about doing this?
Help is much appreciated, thanks!

In your generator, you can just call
generate "some:generator" # can be anything listed by 'rails g'
for example:
module MyGem
class InstallGenerator < Rails::Generators::Base
def run_other_generators
generate "jquery:install" # or whatever you want here
end
end
end
By the way, if you are working on Rails 3 gems, this question can also help out:
Rails 3 generators in gem

Another possibility is to use something like
invoke 'active_record:model', 'foo bar:string baz:float'
which is not as clean as generate, but has one advantage: When your generator gets called via rails destroy, this call -- like may other of Thors actions -- will try to revoke the action of the generator you invoke.
There's a catch however: Probably due to Thors dependency management, this only works once per generator you want to call, meaning that a second invoke of the same generator will do nothing. This can be circumvented by using a statement like
Rails::Generators.invoke 'active_record:model', '...', behavior: behavior
instead. In this case you have to explicitly pass through the behavior of your generator (which is a method returning values like :invoke, :revoke and possibly others, depending on which command -- rails generate, rails destroy, rails update, etc. -- called your generator) to achieve the same result as above. If you don't do this, the generator you call with Rails::Generators.invoke will also be executed when running your generator with rails destroy.
Alternatively you could stick to invoke and try to tamper with Thors invocation system. See also here for example.

Generators are based off of Thor, so you can use the apply method.
This is what the Rails Templater gem does. (Here's a walk through the Rails Templater gem.)

Take a look at the scaffold generator that comes with rails.
/Users/XYZ/sources/rails/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb
def manifest
record do |m|
#....rest of the source is removed for brevity....
m.dependency 'model', [name] + #args, :collision => :skip
end
end
Here the scaffold generator is using the model generator. So take a look at the dependency method. You can find the API docs for it over here.

Related

Use active record in ruby gem

I'm working on a ruby gem which can generate some code in other language. The gem needs to load the models in the current rails app. And it's implemented as a generator which accepts one parameter -- the table name. Inside it, read the columns definition from that table in this way:
tableklass = table_name.to_s.constantize # get the class name from table_name
cols = tableklazz.columns # get columns definitions.
When I run the generator 'rails g mygen Product'. It always gave me the error below:
.../ruby/gems/2.3.0/gems/activesupport-4.2.4/lib/active_support/inflector/methods.rb:261:in `const_get': wrong constant name products (NameError)
How can I fix this error? Or whether is there other better way to do so (read table information to generate some code)?
constantize expects camelized input. I am not sure, what is happening inside your generator, but it looks like constantize finally receives products as a parameter. Safest version of what you are trying to do:
table_name.to_s.singularize.camelize.constantize
Everything below would work:
:products.to_s.singularize.camelize.constantize
:product.to_s.singularize.camelize.constantize
'product'.to_s.singularize.camelize.constantize
Product.to_s.singularize.camelize.constantize

Ruby on Rails: colon in commands

When working with ruby on rails, it is common to use terminal commands like rake db:migrate or rails g devise:install. But what exactly does the : in these commands mean? In rake db:migrate is migrate a parameter or something else?
You can think of the colon as a namespace. Somewhere within Rails there is a rake task file that looks similar to this:
namespace db
task :migrate do...
....
end
end
It's a way to group related tasks together and prevent them from colliding with other tasks. This way you could potentially have devise:migrate, db:migrate, foobar:migrate, etc.
Like Philip explained in his answer when using rake the colon defines the seperator between namespaces/tasks
When using rails g(enerate) it's basically the same. The difference is that Rails generators aren't defined with rake's DSL, instead they're classes.
But to answer your initial Questions: The Colon works as seperator, in both cases, that's it.
In the code the only thing what it's important for is splitting up the string:
https://github.com/rails/rails/blob/4-0-stable/railties/lib/rails/generators.rb#L124
You can find some more infos about generators and how to create your one ones (which definetley will help you understanding the mechanics behind it) in the official Ruby on Rails Guides
//EDIT
Ok, let's take a closer look at the generator lookup process:
First of all there is Rails::Generators.invoke
It receives the namespaces passed on the CLI and split's it up (using the colon)
names = namespace.to_s.split(':')
then it get's the according class by passing the last part of the passed namespace (the actual generator name) and the remaining part joined with a colon again (the namespace path, in our case it's devise) to Rails::Generators::Base.find_by_namespace
if klass = find_by_namespace(names.pop, names.any? && names.join(':'))
This method again will join the base (namespace path) and name (generator name) and push it to an array:
lookups = []
lookups << "#{base}:#{name}" if base
after that it calls Rails::Generators.lookup which will lookup the classes to invoke for the called generator:
lookup(lookups)
which will again will call Rails::Generators.namepaces_to_paths
There's no big magic in that method, it'll simply return an array of two possible source paths for the invoked generator in our case these two are "devise/install/install" and "devise/install".
Whereby those aren't the actual paths rails will check, they are only the part's of it that are depend on the namespace:generator construct.
The lookup method will now take those two, let's call them subpaths, and check for files to require at the following places:
rails/generators/devise/install/install_generator
generators/devise/install/install_generator
rails/generators/devise/install_generator
generators/devise/install_generator
in our case the second path is the desired file, rails require's it and through that the inherited (more about that callback callback on Rails::Generators::Base will get called since Devise::Generators::InstallGenerator inherits from it.
This will add the Class to the subclasses array of Rails::Generators which is mapped to an Hash which will have the format { namespace => klass } and so rails is finally able to get the desired Generator Class
klass = namespaces[namespace]
and start it
klass.start(args, config)

Having default Rails generators call a custom generator

To be clear, here's NOT what I'm trying to:
Have my custom generator call a default Rails generator
Replace a default Rails generator with my own
What I want to do is have my generator be invoked automatically when I call:
rails generate scaffold User name age:integer
I'm not writing a test replacement or anything, it's completely custom. All information I find about generators out there involve one of those first two cases but not what I want to do. As soon as I found hook_for I immediately thought that was exactly what I needed, but it appears to do the opposite -- invokes another Rails generator from inside of my custom one (if I wanted a test file created for my custom generator I'd call hook_for :test_framework and then define a TestUnit::MyCustomGenerator class somewhere).
I suppose I could monkey patch the default scaffold generator to call mine but that feels dirty. I've looked into some gems that do something similar like https://github.com/Skalar/i18n-yaml-generator but trying to convert that to use an initializer and lib/generators isn't working for me. The scaffold_generator runs but mine never gets called.
for me it works from lib/generators/
$ rails g generator scaffold
create lib/generators/scaffold
create lib/generators/scaffold/scaffold_generator.rb
create lib/generators/scaffold/USAGE
create lib/generators/scaffold/templates
$ rails g scaffold
Usage:
rails generate scaffold NAME [options]
....
what/will/it/create
http://guides.rubyonrails.org/generators.html#generators-lookup
another way may be :)
fork railties gem
override Rails::Generators::ScaffoldGenerator class to your liking
install locally or specify source path in Gemfile
also if 'its completely custom', it is just fair to call it a different name, no?

Rails 3.2 generators without a database

I am trying to generate a resource and I have removed all references to Active_record and removed the databse.yml file.
The rails server starts ok but when I try to generate a model:
rails g resource contact
I get the following error:
No value provided for required options '--orm'
Is there a way to specify no database when generating a resource?
There isn't an easy way, no. If you look at the source code for the resource generator, you'll see this part regarding the orm:
# Loads the ORM::Generators::ActiveModel class. This class is responsible
# to tell scaffold entities how to generate an specific method for the
# ORM. Check Rails::Generators::ActiveModel for more information.
def orm_class
#orm_class ||= begin
# Raise an error if the class_option :orm was not defined.
unless self.class.class_options[:orm]
raise "You need to have :orm as class option to invoke orm_class and orm_instance"
end
begin
"#{options[:orm].to_s.camelize}::Generators::ActiveModel".constantize
rescue NameError
Rails::Generators::ActiveModel
end
end
end
So it explicitly rejects any attempt to run this command without an ORM, and if you do specify an ORM, it's looking for ORM::Generators::ActiveModel. And in the comments at the top, it specifies a location to find more information, Rails::Generators::ActiveModel. The comments at the top there explain how to extend it to create an ORM specification.
The only one built-in to rails by default is the ActiveRecord generator.
There is a gem called rails3-generators that includes generators for a number of common libraries, but you can see that for ORMs it only adds functionality for data_mapper, mongo_mapper, mongoid, and active_model.
As far as I know, there is no pre-built ORM generator for "no database". You could write one yourself, if you want, by following the instructions at the top of Rails::Generators::ActiveModel (and using the rails3-generators gem source as a reference of you need it).
But if that seems like too much effort, I'd recommend just telling it to generate using the built-in ActiveRecord generator, and then just manually modifying/removing anything it generated related to that ORM.

Creating Rails Generator For Model/View/Controller/Mailer

I have an app that's heavily form-based. Many of the forms are constructed exactly alike so it seems like a natural fit for a generator. What I want to do is create one that works like this (fictitious example):
rails g request_form name:string phone:string date_of_birth:date
In any case, the standard, empty controllers, helpers, models, and so on won't quite do. I've read the Rails code but it's, frankly, not been a heck of a lot of help in puzzling this out. What I want to do specifically is:
Create a model and migration given the fields specified on the command line
Create a controller and helper, based on my template
Create views based on my templates
Create empty specs
Create a mailer based on my template
Create mailer views based on my templates
I'm getting stalled at square 1: How the heck do I get the ARGV part of the rails g command -- that is, the field names? Then there's square 2: How do I hook into the built-in generators, where appropriate and fill in my own stuff where not?
This is analogous to
rails g scaffold blah:type blah1:type
so I don't think this is biting off more than I can chew...
Any help mucho appreciated!
All inspiration needed in this great gem: https://github.com/ryanb/nifty-generators
The awesome Ryan Bates has a screencast on writing generators in Rails 3, have you watched that?

Resources