Whenever we use the built-in generator generator to start a new generator, Rails uses Rails::Generators::NamedBase as the parent class. While it is the default, it’s not our only option. We can use Rails::Generators::Base instead, but we don’t get quite as much free functionality. So which should we use in what contexts? Let’s take a look at how they work so it’s easy to make the right choice in the future.

Rails::Generators::Base vs. Rails::Generators::NamedBase

Since NamedBase inherits from Base, we’ll start with Base. For the most part, Base handles the low-level setup and integration with the underlying Thor tooling. That is, it helps set up the source and destination roots while including the Thor and Rails actions by default. It also provides the hook_for and remove_hook_for class methods for connecting generators to other generators. Finally, it extends the class_option declaration to support automatically assigning default aliases and values for some of the common options.

As I mentioned, NamedBase inherits from Base, so it provides a superset of functionality. In addition to the shared bits, NamedBase adds a few more features. For example, it pre-defines a name argument—hence the name NamedBase. In conjunction with pre-defining that first name argument, it provides over twenty inflections that create variations of the name value. (Figure 1)

# Primary Inflectionshuman_name # => "Dog"singular_name # => "dog"plural_name # => "dogs"file_path # => "animal/pet/dog"file_name # => "dog"plural_file_name # => "dogs"fixture_file_name # => "dogs"class_name # => "Animal::Pet::Dog"class_path # => ["animal", "pet"]regular_class_path # => ["animal", "pet"]table_name # => "animal_pet_dogs"singular_table_name # => "animal_pet_dog"plural_table_name # => "animal_pet_dogs"i18n_scope # => "animal.pet.dog"# Routes & Redirectsroute_url # => "/animal/pet/dogs"singular_route_name # => "animal_pet_dog"plural_route_name # => "animal_pet_dogs"redirect_resource_name # => "@animal_pet_dog"# URL Helpersurl_helper_prefix # => "animal_pet_dog"index_helper(type: ...) # => "animal_pet_dogs"new_helper(type: ...) # => "new_animal_pet_dog_url"show_helper(arg=..., type: ...) # => "animal_pet_dog_url(@animal_pet_dog)"edit_helper(...) # => "edit_animal_pet_dog_url(@animal_pet_dog)"# Controller Inflections# (Must include Rails::Generators::ResourceHelpers for these.)controller_class_name # => "Animal::Pet::Dogs"controller_class_path # => ["animal", "pet"]controller_file_name # => "dogs"controller_file_path # => "animal/pet/dogs"controller_i18n_scope # => "animal.pet.dogs"controller_name # => "Animal::Pet::Dogs"
Figure 1

When our generator inherits from Rails::Generators::NamedBase, We receive a plethora of inflections of the value in the name argument. Additionally, if we include Rails::Generators::ResourceHelpers, we get a handful of additional controller-related inflections.

↩︎

In addition to the name argument and related inflections, NamedBase has one other particularly interesting ability. If we create an argument named attributes, NamedBase will automatically parse it into a hash of attribute names and types.(Figure 2) Or, to be more specific, it parses the values into GeneratedAttribute instances. Now, your first question might be, “What’s a GeneratedAttribute?” The simple answer is that it’s a basic data structure for Rails to be able to parse attributes from argument strings like supplier:references{polymorphic}:index, price:decimal{10,2}, or title:string. This is how the built-in controllers know how to parse and generate the relevant columns and interface elements for attributes.

railties/lib/rails/generators/named_base.rb module Rails module Generators class NamedBase < Base # ... def initialize(args, *options) # :nodoc: @inside_template = nil # Unfreeze name in case it's given as a frozen string args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen? super assign_names!(name) parse_attributes! if respond_to?(:attributes) end # ... # Convert attributes array into GeneratedAttribute objects. def parse_attributes! self.attributes = (attributes || []).map do |attr| Rails::Generators::GeneratedAttribute.parse(attr) end end # ... end endend
Figure 2

With NamedBase as the parent class, the initializer will call parse_attributes! if the generator responds to the attributes method. If your generator’s attributes method doesn’t return attribute strings, the parsing can end up raising an error.

↩︎

When not expected, this automatic parsing can be a bit of a surprise. However, it’s a great feature when you’re building a generator that needs to handle attributes. Or, more likely, it can serve as a good model for how to design more advanced argument parsing in your own generators.

When should I use one or the other?

For the most part, the default of NamedBase will be the one we want, but there are exceptions. First, if we don’t want or need any arguments, we don’t need the extras provided by NamedBase. Similarly, if we prefer a different initial argument name or type, we might be better off using Base and defining all of our own arguments. I’ve found that I do this most when I’m creating report generators or lower-level generators that handle basic system changes but don’t need to interact heavily with the Rails application.

As for NamedBase, even if we’re not heavily using the inflections, I find that the extras it provides rarely get in the way and usually come in handy at some point. Assuming we can put that first name argument to use, it should be rare to find a case where it makes sense changing the parent class.