generators and migrations in plugins (rails 3) - ruby-on-rails

I am simply trying to create a plugin migration generator without any parameters, like : $rails generate yaffle and this should copy the migration file (lib/generators/yaffle/template/create_yaffle.rb) to db/migrate/[timestamp]_create_yaffle.rb.
The problem I am facing here is, its copying, but without timestamp.
Also, when I run $rails generate yaffle it gives me a message that arguments are not provided, it expects to be in this format rails generate yaffle NAME [options]. I dont want to have any options/arguments, it should just be rails generate yaffle.
What should I do?
I followed the generator used in acts_as_commentable , it looks pretty simple, but I don't know where to modify these settings... can anybody help?
Generator Code:
require 'rails/generators'
require 'rails/generators/migration'
class ThumbitGenerator Rails::Generators::NamedBase
source_root File.expand_path('../templates', __FILE__)
def self.next_migration_number(path)
Time.now.utc.strftime("%Y%m%d%H%M%S")
end
def create_model_file
template "like.rb", "app/models/like.rb"
template "liking.rb", "app/models/liking.rb"
template "create_likes.rb", "db/migrate/create_likes.rb"
template "create_likings.rb", "db/migrate/create_likings.rb"
end
end

Ok, I found the answer...
I was using Rails::Generators::NamedBase instead of Rails::Generators::Base in my generator file! When you use NamedBase, it always expects an argument to be passed (which is the name of initializer) Explanation : guides.rubyonrails.org/generators
And I was using template method instead of migration_template because of which migration files din't produce any migration number Explanation: Rails::Generators::Migration.migration_template
So finally, this worked!
require 'rails/generators'
require 'rails/generators/migration'
class ThumbitGenerator < Rails::Generators::Base
include Rails::Generators::Migration
source_root File.expand_path('../templates', __FILE__)
def self.next_migration_number(path)
Time.now.utc.strftime("%Y%m%d%H%M%S")
end
def create_model_file
template "like.rb", "app/models/like.rb"
template "liking.rb", "app/models/liking.rb"
migration_template "create_likes.rb", "db/migrate/create_likes.rb"
migration_template "create_likings.rb", "db/migrate/create_likings.rb"
end
end

A small polish on the solution - to save yourself the hassle of defining the timestamp for the migration and future proof your generator in case Rails core team decides to use another way of stamping (e.g. SHA hashes truncated to 10 characters), you can require 'rails/generators/active_record' and extend ActiveRecord::Generators::Migration like this:
require 'rails/generators'
require 'rails/generators/migration'
require 'rails/generators/active_record'
class ThumbitGenerator < Rails::Generators::Base
include Rails::Generators::Migration
extend ActiveRecord::Generators::Migration
source_root File.expand_path('../templates', __FILE__)
def create_model_file
template "like.rb", "app/models/like.rb"
template "liking.rb", "app/models/liking.rb"
migration_template "create_likes.rb", "db/migrate/create_likes.rb"
migration_template "create_likings.rb", "db/migrate/create_likings.rb"
end
end
UPDATE In Rails 4 ActiveRecord::Generators::Migration is no longer a module, so use instead:
require 'rails/generators'
require 'rails/generators/migration'
require 'rails/generators/active_record'
class ThumbitGenerator < Rails::Generators::Base
include Rails::Generators::Migration
# Implement the required interface for Rails::Generators::Migration
def self.next_migration_number(dirname)
ActiveRecord::Generators::Base.next_migration_number(dirname)
end
source_root File.expand_path('../templates', __FILE__)
def create_model_file
template "like.rb", "app/models/like.rb"
template "liking.rb", "app/models/liking.rb"
migration_template "create_likes.rb", "db/migrate/create_likes.rb"
migration_template "create_likings.rb", "db/migrate/create_likings.rb"
end
end

you can simply inherit from ActiveRecord::Generators::Base and everything will work

Related

Include module in all MiniTest tests like in RSpec

In RSpec I could create helper modules in /spec/support/...
module MyHelpers
def help1
puts "hi"
end
end
and include it in every spec like this:
RSpec.configure do |config|
config.include(MyHelpers)
end
and use it in my tests like this:
describe User do
it "does something" do
help1
end
end
How can I include a module into all MiniTest tests without repeating myself in every test?
From the Minitest README:
=== How to share code across test classes?
Use a module. That's exactly what they're for:
module UsefulStuff
def useful_method
# ...
end
end
describe Blah do
include UsefulStuff
def test_whatever
# useful_method available here
end
end
Just define the module in a file and use require to pull it in. For example, if 'UsefulStuff' is defined in test/support/useful_stuff.rb, you might have require 'support/useful_stuff' in either your individual test file.
UPDATE:
To clarify, in your existing test/test_helper.rb file or in a new test/test_helper.rb file you create, include the following:
Dir[Rails.root.join("test/support/**/*.rb")].each { |f| require f }
which will require all files in the test/support subdirectory.
Then, in each of your individual test files just add
require 'test_helper'
This is exactly analogous to RSpec, where you have a require 'spec_helper' line at the top of each spec file.
minitest does not provide a way to include or extend a module into every test class in the same way RSpec does.
Your best bet is going to be to re-open the test case class (differs, depending on the minitest version you're using) and include whatever modules you want there. You probably want to do this in either your test_helper or in a dedicated file that lets everyone else know you're monkey-patching minitest. Here are some examples:
For minitest ~> 4 (what you get with the Ruby Standard Library)
module MiniTest
class Unit
class TestCase
include MyHelpers
end
end
end
For minitest 5+
module Minitest
class Test
include MyHelperz
end
end
You can then use the included methods in your test:
class MyTest < Minitest::Test # or MiniTest::Unit::TestCase
def test_something
help1
# ...snip...
end
end
Hope this answers your question!
One thing I will do is create my own Test class inheriting from Minitest::Test. This allows me to do any sort of configuration on my base test class and keeping it isolated to my own project1.
# test_helper.rb
include 'helpers/my_useful_module'
module MyGem
class Test < Minitest::Test
include MyUsefulModule
end
end
# my_test.rb
include 'test_helper'
module MyGem
MyTest < Test
end
end
1 This is most likely unneeded, but I like keeping all my gem code isolated.

Add new migrations from Rails engine gem to app via generator

I'm building a Rails engine in a ruby gem. It includes some migrations right now that are called when you run:
rails g myengine:install
The code in the generator is as follows:
module MyEngine
module Generators
class InstallGenerator < ::Rails::Generators::Base
include Rails::Generators::Migration
source_root File.expand_path('../templates', __FILE__)
# ...
def copy_migrations
migration_template "migrations/migration1.rb", "db/migrate/migration1.rb"
migration_template "migrations/migration2.rb", "db/migrate/migration2.rb"
end
# ...
end
end
end
However, if I run rails g myengine:install again, it fails with this error:
Another migration is already named migration1: /Users/jh/Code/Web/demoapp/db/migrate/20130327222221_migration1.rb
I want it to just silently ignore the fact that there's already a migration and continue on to the next migration. What would be the best way to do this?
EDIT:
Per Dmitry's answer, this was my solution:
def copy_migrations
copy_migration "migration1"
copy_migration "migration2"
end
protected
def copy_migration(filename)
if self.class.migration_exists?("db/migrate", "#{filename}")
say_status("skipped", "Migration #{filename}.rb already exists")
else
migration_template "migrations/#{filename}.rb", "db/migrate/#{filename}.rb"
end
end
Using migration_template in Rails as example, you could perhaps check for destination = self.class.migration_exists?(migration_dir, #migration_file_name) and if migration already exists, skip over making migration_template call.

Testing without loading Rails: faked class need so be 'unfaked' after tests run

I've refactored part of my test suite to not load rails when performing tests. The code below is an example of a test file that loads only selection pieces of Rails. It also fakes the "project" class. My problem is that this faked project class ends up overriding the normal project class and all other tests that involve the project class now fail.
How do I un-override my project class after this test file runs?
require 'active_model'
require 'active_model/validations'
require 'active_record/callbacks'
require 'active_support/core_ext/string'
class Project
include ActiveModel::Validations
include ActiveRecord::Callbacks
def initialize(attributes = {})
#general_media = attributes[:general_media]
end
attr_accessor :general_media
end
require_relative '../../../app/models/project/media.rb'
UPDATE: I think this comes close to what I need, except that I'm getting an error about Project being an uninitialized constant. I must be instantiating this test class incorrectly.
require 'active_model'
require 'active_model/validations'
require 'active_record/callbacks'
require 'active_support/core_ext/string' #used for '.blank?' method
require_relative '../../../app/models/project/media.rb'
describe Project::Media do
before(:all) do
class Project
include ActiveModel::Validations
include ActiveRecord::Callbacks
def initialize(attributes = {})
#general_media = attributes[:general_media]
end
attr_accessor :general_media
end
end
after(:all) { Object.send(:remove_const, :Project) }
#then all the tests
You should be able to un-define a class with Module#remove_const.
Object.send(:remove_const, :Project)
Its a private method so you'll need to use send rather than a regular method call.
UPDATE:
Perhaps try the following:
require 'active_model'
require 'active_model/validations'
require 'active_record/callbacks'
require 'active_support/core_ext/string' #used for '.blank?' method
class Project
include ActiveModel::Validations
include ActiveRecord::Callbacks
def initialize(attributes = {})
#general_media = attributes[:general_media]
end
attr_accessor :general_media
end
require_relative '../../../app/models/project/media.rb'
describe Project::Media do
after(:all) { Object.send(:remove_const, :Project) }
...
You will need to declare the Project class before the describe block if the subject depends on it. Also assuming your Media model depends on it before you require that.
Later tests for the Project class will need to reload it, assuming they are separate tests you can just require your project model class in that test file if you want a minimal (fast) test, or via the normal spec_helper if you want to load the whole Rails application (slow).
As discussed in comments it might be easier to simply stub out the Project class rather than redefine it.

Creating a rails 3 generator

I'm creating a rails generator:
class TaggableGenerator < Rails::Generators::NamedBase
source_root File.expand_path('../templates', __FILE__)
hook_for :orm, :as => "model"
end
Everything works fine, but I would like to set the fields created in the models and create multiple models, I can't find anything about how to do it (I got the above code from looking at the devise generators) preferably I'd like it to me orm generic (but its not that important).
Here is some links that may help you:
http://railscasts.com/episodes/218-making-generators-in-rails-3
http://guides.rubyonrails.org/generators.html
Basically you just need to add methods in your class to do things you want (all the public methods will get called when the generator is called), here is an example from rails sources:
class AssetsGenerator < Rails::Generators::NamedBase
source_root File.expand_path("../templates", __FILE__)
def copy_stylesheet
copy_file "stylesheet.css", File.join('app/assets/stylesheets', class_path, "#{file_name}.css")
end
end
the copy_file comes from Thor, you wan see a list of availables methods in the Thor reference: http://textmate.rubyforge.org/thor/Thor/Actions.html

how to create your own rails generator without needing an argument?

It seems that I am stuck figuring out so that my generator doesn't need an argument. So for instance my generator code is this:
class MyGenerator < Rails::Generators::NamedBase
source_root File.expand_path('../templates', __FILE__)
def generate_stylesheet
copy_file "my.css", "public/stylesheets/my.css"
end
end
But when I do rails g my rails always asks for an extra argument. Can you show me how so it doesn't need an extra argument?
Thanks.
You have to use class MyGenerator < Rails::Generators::Base instead of class MyGenerator < Rails::Generators::NamedBase

Resources