Rails 4 engine with nested namespace - ruby-on-rails

I could not find a way to generate engines with nested namespaces under rails. Every time I do that, I basically have to edit and move around the generated files by hand.
Is there really no support for nested namespaces in rails? Seems unlikely.
At the company we namespace everything like this:
CompanyName::SerivceName::Module
So when I'm working on Service1, and making the engine which will be integrated into the app that customer support uses to play around with the users and data of that service on customer requests, I'd like to create that engine under
CompanyName::Serive1::CustomerSupport
However rails seems to be unable to do that.
Using rails plugin new a::b::blah is not accepted:
akovanm0:test avandra$ rails plugin new a::b::blah -T --dummy-path=spec/dummy --mountable --full --mountable
Invalid plugin name a::b::blah. Please give a name which use only alphabetic or numeric or "_" characters.
Specifying rails plugin new a/b/blah generates an engine, but has the same output as rails plugin new blah
Specifying rails plugin new a_b_blah generates an engine with the literal name a_b_blah, not namespaced. (and the actual name is camelcased to ABBlah)
What I'd like to achieve is an engine whose controllers, models and views are generated under the a::b::blah namespace, and it is mountable the same way.
I want all generated controllers to go under app/controllers/a/b/blah, the models to go under app/models/a/b/blah, and so on...
Is there a way to achieve this?

For anyone reading this in 2022, I believe later versions of Rails handle engine names with dashes as separate namespaces.
rails plugin new parent_engine-sub_engine --mountable
Will create the following engine.rb:
module ParentEngine
module SubEngine
class Engine < ::Rails::Engine
isolate_namespace ParentEngine::SubEngine
end
end
end

You need to create engine with mountable option enabled like this,
rails plugin new engine_name --mountable.
It will add isolate_namespace EngineName method call in lib/engine_name/engine.rb to isolate engine namespace.

I think you can't do that :(
EDIT: Look at the bottom of the answer, I have modified the rails plugin generator just to do it :)
If you look carefully to the source (https://github.com/rails/rails/blob/5f07366bed77116dbfbb5b98d1cdf6c61b3dfc9b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb#L299) you will see that the plugin name is just the basename of the destination folder.
def original_name
#original_name ||= File.basename(destination_root)
end
So if you write rails plugin new a/b/c then the plugin will be created at the a/b/c subfolder in your current folder but the name will be just c :(
If you override that original_name method to return a/b/c as desired then you will need to fight both the valid_const? method (https://github.com/rails/rails/blob/5f07366bed77116dbfbb5b98d1cdf6c61b3dfc9b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb#L307) that validates the format name and accepts "only alphabetic or numeric or _ characters." and the templates that creates the modules.
def valid_const?
if original_name =~ /[^0-9a-zA-Z_]+/
raise Error, "Invalid plugin name #{original_name}. Please give a name which use only alphabetic or numeric or \"_\" characters."
elsif camelized =~ /^\d/
raise Error, "Invalid plugin name #{original_name}. Please give a name which does not start with numbers."
elsif RESERVED_NAMES.include?(name)
raise Error, "Invalid plugin name #{original_name}. Please give a name which does not match one of the reserved rails words."
elsif Object.const_defined?(camelized)
raise Error, "Invalid plugin name #{original_name}, constant #{camelized} is already in use. Please choose another plugin name."
end
end
I'm thinking on using a plugin template (http://edgeguides.rubyonrails.org/rails_application_templates.html) for my namespaced plugins instead :(
EDIT: I lied about what methods you would have to fight. It's not the name method, it's the templates
EDIT (II): I have modified the plugin_new folder so nested namespaces are allowed. You have it here: https://github.com/brenes/nested-plugin-generator
I would appreciate any feedback :)

Try https://github.com/T-Dnzt/Modular-Engine or create your own generator

Related

Rails generator listed, but not found when running

I am in process of writing a Rails Engine, and as part of it I have a generator script that copies some migration files.
The generator is located here
lib/generators/install/install_generator.rb
The generator looks like this
module Calligraph
class InstallGenerator < Rails::Generators::NamedBase
...
...
end
end
In the host application, if I do a rails g, I see this along with other generators.
Calligraph:
calligraph:install
If I hit
rails g calligraph:install
it comes back with
Could not find generator calligraph:install.
Any idea on what's missing/wrong?
Try moving your generator to
lib/generators/calligraph/install/install_generator.rb.
Because your generator is defined within a module, Rails expects it to be contained within a subdirectory with the same name, just like namespaced controllers or other Rails extensions that live within modules.
Alternatively, you sometimes get a "could not find generator" error if you have a syntax problem in your generator, but the console would also spit out a trace and details on what that error would be if that were the case.
For your generator to work correctly, you also need to require rails/generators somewhere in your engine. I usually do it at the top of the generator file, but since rails g is picking up your generator it seems like you have taken care of it somewhere.

Extend a Model from a Rails Engine (not replace it)

I have a Rails app that uses a gem called ActsAsTaggableOnSteroids, which is a Rails Engine. Specifically, I'm using PavelNartov's fork of the gem. But nevermind that.
I need to add specific functionality to the Tag model, which is supplied by the engine.
But, according to my understanding of Rails engines and the magical loading functionality in Rails, if I put a file called "tag.rb" in my models directory, then it will completely replace the one from the Engine.
Ideally, I would be able to do something like:
class Tag < ActsAsTaggable::Tag
# my stuff
end
...but alas, that doesn't work because the model supplied by the engine is not namespaced.
So, I came up with this nightmare, which I put in app/models/tag.rb:
path = ActsAsTaggable::Engine.config.eager_load_paths.grep(/models/).first
require File.join(path, 'tag')
Tag.class_eval { include TagConcern }
But there has to be a better way! I feel like I'm missing something. I'd prefer not to add this strangeness to my app if possible.
Just require the file by looking up the path of the gem's model:
require File.join(Gem::Specification.find_by_name("bborn-acts_as_taggable_on_steroids").gem_dir, 'app/models/tag')
Tag.class_eval do
# ...
end

Custom Views Scaffolding in Rails Engines

I'm trying to get custom scaffolding working from my engine.
I followed some tutorial on customizing Rails 3.2 scaffolding in a normal Rails App and put my customized templates in the engines /lib/templates/erb/scaffold directory but they don't get picked up by the app that includes the engine. Any suggestions?
Update:
I also tried to override the Rails ScaffoldGenerator's source_path and tried some other paths to put my template in, like:
lib/rails/generators/erb/scaffold/templates
zarazan's answer got me most of the way there, but there are a couple of things wrong with it. Here's what worked for me:
class Engine < Rails::Engine
config.generators do |g|
g.templates.unshift File::expand_path('../../templates', __FILE__)
end
end
Note that this goes in the generators section, not app_generators, and that the path is slightly different.
Also, I think the correct path to store your templates is lib/templates/erb/scaffold, optionally replacing erb with whatever language you are using (like haml or slim.) I know this works for slim. The file names are {_form,edit,index,new,show}.html.erb.
In the file that you declare your engine use this command:
class Engine < Rails::Engine
config.app_generators do |g|
g.templates.unshift File::expand_path('../templates', __FILE__)
end
end
It should shift the preference of what template folder Rails uses by default.
Now just put the template files in lib/templates/erb/scaffold/template_name.erb
Where template_name is one of the following: _form.html.erb, edit.html.erb, index.html.erb, new.html.erb, show.html.erb
Once you include the gem you should be able to use the rails generate scaffold command as normal.
Here is an example of an engine that overrides the default scaffolding in rails:
https://github.com/brocktoncg/gemboree
This is where the template directory is located:
https://github.com/brocktoncg/gemboree/tree/master/lib/templates/erb/scaffold
Are you talking about a controller template? Than you are using the wrong directory. Save your template at
lib/templates/rails/scaffold_controller/controller.rb
Have a look at http://xyzpub.com/en/ruby-on-rails/3.2/templates.html for an example.

How to implement generators for a plugin located at the `lib/<plugin_name>` directory?

I am using Ruby on Rails 3.2.2. I have implemented a Something plugin (it is almost a gem, but is not a gem) and all related files are in the lib/something directory. Since I would like to automate code generation related to that plugin, I came up with Ruby on Rails Generators. So, for the Something plugin, I am looking for implementing my own generators in the lib/something directory.
How should I make that and what are prescriptions? That is, for example, what rails generate command line should be invoked to properly generate all needed files in the lib/something directory? generators would still work with plugins (not gem)? what are advices about this matter?
I would make it a gem. I've made generators using gems, but I don't know if the generators would still work with plugins.
If you are having difficulty with the command line, I am guessing that you don't need any argument. (If you need an argument, I could copy the provided templates, and if I needed some other argument I'd be lost, so my advise is limited to non-argument.)
I have a generator gem which generates migration files needed for another gem. It checks if the migration with a given root name (w/o the timestamp prefix) is in db/migrate, and otherwise creates it.
Here is my code. I think this example is the help you need.
class ItrcClientFilesGenerator < Rails::Generators::Base
source_root(File.dirname(__FILE__) + "/../src")
desc "Generator to create migrations for needed db tables"
def create_itrc_client_files
prefix = DateTime.now.strftime("%Y%m%d%H%M")
existing_migrations =
Dir.glob("db/migrate/*itrc*").map do |path|
File.basename(path).gsub(/^\d*_/, '')
end
Dir.glob(File.dirname(__FILE__) + "/../src/*").sort.each_with_index do |src_filepath, index|
src_filename = File.basename(src_filepath)
unless existing_migrations.include?(src_filename.gsub(/^\d*_/, '')) then
this_prefix = "#{prefix}#{'%02i' % index}_"
dst_filename = src_filename.gsub(/^\d*_/, this_prefix)
copy_file(src_filename, "db/migrate/" + dst_filename)
end
end
end
end

Accessing the app name from inside a rails template when generating rails app

I'm messing around with rails 2.3 templates and want to be able to use the app name as a variable inside my template, so when I use...
rails appname -m path/to/template.rb
...I want to be able to access appname inside template.rb. Anyone know how to do this?
Thanks
I was looking for an answer to this question. unfortunately the answer above (#root) doesn't seem to work in Rails 3.
Here's the variables you can access in Rails 3 app templates (even easier):
#app_name
#app_path
Thanks for the answers. Mike Woodhouse, you were so close. Turns out, all you need to do to access the appname from inside your rails template is...
#root.split('/').last
The #root variable is the first thing created when initializing templates and is available inside your rails templates. RAILS_ROOT does not work.
In Rails 3, use the app_name attribute.
See the documentation for the Rails::Generators::AppGenerator.
I ran into a similar problem, none of the variables listed above were available to me in Rails 4. I found that #name was available while running
rails plugin new engines/dummy -m my_template.rb
There are other useful variables available from within the template. You can see for yourself and play around by utilizing pry. Inside my template I added
require 'pry'; binding.pry
and then ran ls to show a list of available instance variables
ls -i
instance variables:
#_initializer #app_path #behavior #destination_stack #extra_entries #name #output_buffer #shell
#_invocations #args #builder #dummy_path #gem_filter #options #rails_template #source_paths
#after_bundle_callbacks #author #camelized #email #in_group #original_name #shebang
There's probably a more straightforward way, but this seems to work:
RAILS_ROOT.split('/').last
EDIT: Bleah - this got voted down once, and the voter was right. If I'd read the question more carefully, I'd have noticed the 2.3 and template.rb elements. Apologies.
I suspect that RAILS_ROOT won't have been created at the point that you need the app name. Looking at ruby\lib\ruby\gems\1.8\gems\rails-2.2.2\bin\rails, however, almost the first thing that happens is this:
app_path = ARGV.first
It's used at the end of the script to allow a chdir and freeze to be done if needed - I didn't know I could insta-freeze at creation, so I learned something new at least. ARGV then gets used here:
Rails::Generator::Scripts::Generate.new.run(ARGV, :generator => 'app')
which quickly gets us to the place where ARGV is really handled:
rails-2.3.1\lib\rails_generator\scripts.rb
where I see
Rails::Generator::Base.instance(options[:generator], args, options).command(options[:command]).invoke!
Somewhere below here is probably where the templating gets handled. I'm afraid I'm at a very early stage with 2.3 and templating is an area that I haven't looked at yet.
Does that help any better than my first effort?
RAILS_ROOT will give you the absolute path to your root directory. Your app name will be the portion of the string after the final '/' which you can grab in any number of ways.
EDIT: Not quite enough to get the job done. Mike and Dan iron it out below.
I believe the preferred way now is to call Rails.root and no longer RAILS_ROOT. Apparently someone on planet rails has an aversion to uppercase or some similar important reason. As of 2.3.5 they both appear to work.
I was getting error
`template': undefined local variable or method `app_name'
ruby 1.9.2p290, rails 3.2.11, thor 0.18.0, Windows
but with rails 2.3 generator:
class DynanavGenerator < Rails::Generators::Base
(can't be sure whether this error happened under rails 3.0.9 or earlier)
changed class definition to be:
class DynanavGenerator < Rails::Generators::NamedBase
which then gave:
No value provided for required arguments 'name'
I then added a 'name' ("something" below):
rails generate dynanav something --force
which gave the original error, so I then added:
def app_name
#name.titleize
end
to the class and all was well.
As of Rails 4 (maybe earlier versions?), use Rails.application.class to get the application name. For example, if your app is named Fizzbuzz, here are a few ways you might access it:
rails(development)> Rails.application.class
=> Fizzbuzz::Application
rails(development)> Rails.application.class.name
=> "Fizzbuzz::Application"
rails(development)> Rails.application.class.parent
=> Fizzbuzz
rails(development)> Rails.application.class.parent.to_s
=> "Fizzbuzz"

Resources