I'm writing a Rails 3.1 engine and testing with RSpec 2. When I use rails generate, I get spec files generated for me automatically, which is so convenient:
$ rails g model Foo
invoke active_record
create db/migrate/20111102042931_create_myengine_foos.rb
create app/models/myengine/foo.rb
invoke rspec
create spec/models/myengine/foo_spec.rb
However, to make the generated specs play nicely with my isolated namespace, I have to wrap the spec each time manually in a module:
require 'spec_helper'
module MyEngine
describe Foo do
it "should be round"
...
end
end
I would love to know if there's a clean and easy way to modify the automatically generated spec 'templates' so that I don't have to wrap the spec in Module MyEngine each time I generate a new model or controller.
You can copy RSpec's templates using a Rake task like:
namespace :spec do
namespace :templates do
# desc "Copy all the templates from rspec to the application directory for customization. Already existing local copies will be overwritten"
task :copy do
generators_lib = File.join(Gem.loaded_specs["rspec-rails"].full_gem_path, "lib/generators")
project_templates = "#{Rails.root}/lib/templates"
default_templates = { "rspec" => %w{controller helper integration mailer model observer scaffold view} }
default_templates.each do |type, names|
local_template_type_dir = File.join(project_templates, type)
FileUtils.mkdir_p local_template_type_dir
names.each do |name|
dst_name = File.join(local_template_type_dir, name)
src_name = File.join(generators_lib, type, name, "templates")
FileUtils.cp_r src_name, dst_name
end
end
end
end
end
Then you can modify the code in #{Rails.root}/lib/templates/rspec/model/model_spec.rb to include the module name.
Copy the '/lib/generators/rspec/scaffold/templates/controller_spec.rb' file from the rspec-rails gem to your 'app/lib/templates/rspec/scaffold' folder, then customize it. Obviously, if you move to a new version of rspec-rails you will want to make sure that your customized template hasn't gotten stale.
Related
I followed this tutorial to create a rails plugin.
Section 3 explains how to add a method to string that is available anywhere in a Rails application. Now for my case, I want to be able to access rails methods in the plugin itself.
For example in lib/plugin/my_plugin/my_plugin.rb I want to use:
Rails.application.eager_load!
But every time running the gem with an executable it throws following error:
undefined method `eager_load!' for nil:NilClass (NoMethodError)
Now I know this error is thrown because there's no Rails application, but I added the plugin to a Rails application and it also doesn't seem to work (same error) and can't find another way to get this working. Am I approaching this problem the wrong way or is there even a better way?
lib/plugin/my_plugin/my_plugin.rb:
require 'rails'
class MyPlugin
def fetch_models
arr_models = []
Rails.application.eager_load!
::ApplicationRecord.descendants.each do |model|
arr_models[] << model
end
end
end
Steps reproduce:
Create a new plugin rails plugin new custom
Add lib/custom/generator.rb
lib/custom/generator.rb:
require 'faker'
require 'rails'
class Generator
def fetch_models
arr_models = []
Rails.application.eager_load!
ApplicationRecord.descendants.each do |model|
arr_models[] << model
end
end
end
Edit lib/custom.rb
lib/custom.rb:
require "custom/generator"
require "custom/version"
require "custom/railtie"
module Custom
def self.generate
generator = Generator.new
models = generator.fetch_models
end
end
Add executable in bin/
bin/custom:
#!/usr/bin/env ruby
require 'custom'
Custom.generate
Edit custom.gemspec so that bin/test passes
Execute following commands:
$ bundle exec rake build
$ bundle exec rake install
Plugins usually are built to extend core classes.
Using the generator and following the linked guide at my_plugin/lib/my_plugin/rails_core_ext.rb you have to use class you want to extend.
Your code become
class RailsPlayground::Application
def fetch_models
arr_models = []
Rails.application.eager_load!
ApplicationRecord.descendants.each do |model|
arr_models[] << model
end
end
end
or
class RailsPlayground::Application
def fetch_models
Rails.application.eager_load!
ApplicationRecord.descendants
end
end
I want to properly use module files in a spec/support/ rspec directory for a typical Ruby on Rails project.
# spec/support/page_objects/foo/bar.rb
module Foo
module Bar
def hello
"hello world"
end
end
end
# spec/support/page_objects/foo/foo_bar.rb
class FooBar
include Foo::Bar
end
# spec/system/foo_bars/create_spec.rb
RSpec.describe "Create FooBar", type: :system do
it "returns hello world"
expect(FooBar.new.hello).to eq "hello world"
end
end
I would expect rspec spec/foo_bars/create_spec.rb to pass, however I am receiving uninitialized constant Foo::Bar.
The only way I can get it working is to require the module in the class file.
# spec/support/page_objects/foo/foo_bar.rb
require_relative("bar")
class FooBar
include Foo::Bar
end
Is there a way to properly use module files in spec/support to prevent having to explicitly call require?
Nothing in spec is autoloaded by default.
Typically one adds a line to load spec/support files in spec/rails_helper.rb
Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f }
Note that this will add load time to all test runs regardless of whether they use your support files. This is fine for general use support files, but not such a good idea for support files specific to a single class. Those test classes will pollute every test.
Instead, consider writing them as a shared context and including as needed. Put them in a shared_context directory. You can also load all shared contexts and examples.
Dir[Rails.root.join('spec/**/shared*/*.rb')].sort.each { |f| require f }
Note that spec/support/page_objects/foo/foo_bar.rb would normally be for Foo::FooBar. FooBar should go into spec/support/page_objects/foo_bar.rb. This will have no material effect on your code, but it's good to have naming consistent with Rails standards.
rails g task folder1/namespace1 task1
Above command will create the task1.rake inside lib/tasks/task1.rake
But, I need to keep my task1.rake inside lib/tasks/folder1/task1.rake
The rails g task generator does not work this way.
Here are the code for the TaskGenerator class
module Rails
module Generators
class TaskGenerator < NamedBase # :nodoc:
argument :actions, type: :array, default: [], banner: "action action"
def create_task_files
template 'task.rb', File.join('lib/tasks', "#{file_name}.rake")
end
end
end
end
As you see the lib/tasks path is hardcoded and you can not pass in options to alter the path.
I think this might be a great addition to the TaskGenerator class.
The answer to your question is that you have to make the folders manually.
Using rails and rspec it's easy to have rspec generate the necessary files for me when I'm using the rails generate command with models/views/controllers. But now I want to write specs for a module I wrote. The module is in /lib/my_module.rb so I created a spec in /spec/lib/my_module_spec.rb
The problem I'm having is that when I try to do rspec spec/ the file my_module_spec.rb is run but the reference to my module in lib/my_module.rb can't be found. What's the right way to do this?
Just FYI the my_module_spec.rb file does have require 'spec_helper' in it already
require 'spec_helper'
describe "my_module" do
it "first test"
result = MyModule.some_method # fails here because it can't find MyModule
end
end
You could try including the module and maybe wrapping it in an object
require 'spec_helper'
#EDIT according to
# http://stackoverflow.com/users/483040/jaydel
require "#{Rails.root}/lib/my_module.rb"
describe MyModule do
let(:wrapper){
class MyModuleWrapper
include MyModule
end
MyModuleWrapper.new
}
it "#some_method" do
wrapper.some_method.should == "something"
end
end
Does your module contain class methods or instance methods? Remember that only class methods will be available via
MyModule.some_method
meaning that some_method is defined as
def self.some_method
...
end
If your module contains instance methods, then use Jasper's solution above. Hope that clarifies.
Put the following in your config/application.rb file:
config.autoload_paths += %W(#{Rails.root}/lib)
I was just wrestling with the same problem, and the above worked for me. There's not really any reason you should have to jump through hoops to be able to access your lib/ files from RSpec and write tests for them.
Can I use helper methods in rake?
Yes, you can. You simply need to require the helper file and then include that helper inside your rake file (which actually a helper is a mixin that we can include).
For example, here I have an application_helper file inside app/helpers directory that contains this:
module ApplicationHelper
def hi
"hi"
end
end
so here is my rake file's content:
require "#{Rails.root}/app/helpers/application_helper"
include ApplicationHelper
namespace :help do
task :hi do
puts hi
end
end
and here is the result on my Terminal:
god:helper-in-rake arie$ rake help:hi
hi
As lfender6445 mentioned, using include ApplicationHelper, as in Arie's answer, is going to pollute the top-level scope containing your tasks.
Here's an alternative solution that avoids that unsafe side-effect.
First, we should not put our task helpers in app/helpers. To quote from "Where Do I Put My Code?" at codefol.io:
Rails “helpers” are very specifically view helpers. They’re automatically included in views, but not in controllers or models. That’s on purpose.
Since app/helpers is intended for view helpers, and Rake tasks are not views, we should put our task helpers somewhere else. I recommend lib/task_helpers.
In lib/task_helpers/application_helper.rb:
module ApplicationHelper
def self.hi
"hi"
end
end
In your Rakefile or a .rake file in lib/tasks:
require 'task_helpers/application_helper'
namespace :help do
task :hi do
puts ApplicationHelper.hi
end
end
I'm not sure if the question was originally asking about including view helpers in rake tasks, or just "helper methods" for Rake tasks. But it's not ideal to share a helper file across both views and tasks. Instead, take the helpers you want to use in both views and tasks, and move them to a separate dependency that's included both in a view helper, and in a task helper.