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)
Related
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
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?
Given a rake task that references both a namespaced and non-namespaced model:
namespace :thing do
task :thingo => :environment do
Klass.first.some_method
Namespaced::Klass.first.some_other_method
end
end
Using ruby 1.9.2, rails 3.0.9, and rake 0.9.2, this yields an exception, like so:
undefined method 'some_other_method' for #<Klass:0x007fcfafbaa6e0>
Two things:
Why doesn't rails return the proper namespacing in the rake environment (in a debugger session), but it does in a console session?
Why does changing the order of reference work? (That is, if the environment is already calling "Namespaced::Klass" as "Klass", then calling "Klass" should fail with undefined method 'some_method' for #<Klass:0x007fcfafbaa6e0> right?
By the way, I've tried ::Namespaced::Klass.first.some_other_method
If the answer isn't simple, I'll put together a test app - let me know! :-)
First, some background on metaphor shear - two different kinds of namespaces:
Although Rake Namepsaces and Ruby Namespaces share the word Namespace, they are separate concepts. Rake namespaces are just organizing containers for Rake Tasks, not Ruby namespaces/modules. So code inside your thing:thingo rake task is actually executing at the top-level Ruby namespace.
Second: If Klass is a single class not in a namespace, you can reference it directly. If the class exists as Foo::Klass then you'll need to use the fully-qualified Foo::Klass reference unless the scope of the reference is already within the Foo namespace.
Because Rake namespaces aren't Ruby modules, you are not in the context of a Ruby namespace within your task. This is why Klass.some_method works if Klass isn't in a module.
If this doesn't explain the question, please post the class definition for Klass including any module/namespace membership.
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.
I have an app that I've converted over from another cms. The old URLs were being stored in the database like so:
/this-is-an-old-permalink/
And I need them to be like this:
this-is-an-old-permalink
Note the absence of forward slashes. What is the easiest way to go about removing them?
I'm not necessarily looking for the exact code to do it (although that'd be nice!) -- I'm asking also as a Rails newb: What is the best method to go about doing things like this? I've only really worked with Rails in setting up a model, controller, views and outputting data. I haven't had to do any processing like this. Would it go in the model? Any help is appreciated!
edit
Do I need to get all records, loop through them, do regex on that one field and then save it?
Since you're likely only going to write this once, your best bet is to create a script for it within lib, or to write a migration for it. I recommend the latter, because it will then be executed automatically with rake db:migrate if you restore from your old backup at a later date. You can then use all your standard Model processing tricks (like you would use on a Controller) within the migration without exposing the substitution code to a Controller.
EDIT:
You can add the following to a new file within lib/tasks to create a new rake task for this called db:substitute_slashes:
namespace :db do
desc "Remove slashes from old-style URLs"
task :substitute_slashes => :environment do
Modelname.find(:all).each do |obj|
obj.fieldname.gsub!(/regex here/,'')
obj.save!
end
end
end
The exclamation on the end of save! means it will throw an exception if the resulting object fails validation, which is a good thing to check for in this case.
You would then be able to execute this with the command rake db:substitute_slashes.