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
Related
When I try to generate a model using the next sentence:
rails g model SupplyRequestProfile name:string{50} active:boolean
It fails, and shows the next single line
No value provided for option '--orm'
But, when I use, skipping the fields and types:
rails g model SupplyRequestProfile
It does the job and all is normal.
I have looked if I use a reserved word of rails for my propertys in http://reservedwords.herokuapp.com/ but there is no one that im using.
The problem is not the config/application.rb, all is ok there.
So, I dont know what could be happen here, but it is like some of my property names or types are wrong or causing some strange behavior on rails generation.
What could be wrong?
Simply putting the fields names with their types inside double quotes will work:
rails generate model SupplyRequestProfile "name:string{50}" "active:boolean"
I am trying to access all records of a table that has an underscore in its name.
For example if I have table in my schema that is called trips I can do Trip.all in rails console. But what do I do if my table name contains an underscore (e.g. users_foods)
I tried the following options:
Users_food.all
User_food.all
User_foods.all
etc.
All of the above did not work, any suggestions?
Figured it out
One can access the data with UserFood
For a table named users_foods, ActiveModel should provide you with a corresponding Rails model UsersFood, to fit Ruby/Rails object naming convention. Try that.
Class names Users_food or User_food, etc. do not conform to Ruby convention.
I had the same problem when creating bonus_histories table. And didn't work any of the answers until I found out why.
I have made only rails g migration BonusHistory
and it was the problem. My rails console didn't find BonusHistory at all because I had no model.
So I had to first rollback the migration rake db:rollback STEP=1, then deleted the migration file and finally made
rails g model BonusHistory
and after migrating that table, when I enter rails console, I can successfully ask for BonusHistory.count
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)
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?
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.