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.
Related
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.
I want to define view helpers in Rails 5.2.0 on runtime (from within some code that lies within my lib folder and / or some initializer) and I came up with this approach so far:
def new_module
Module.new do
def self.create_method(name, &block)
define_method(name, &block)
end
end
end
def define_dynamic_helper(name, &block)
helpers = new_module
helpers.create_method(name, &block)
ActionView::Base.send :include, helpers
end
Now that I can define dynamic modules that get include into ActionView::Base on runtime, I call them e.g. in my controller like this:
define_dynamic_helper("my_helper") do
"some data"
end
And my view uses the helper like this
<%= my_helper %>
But this has a drawback during development: When I remove the line that defines my helper, it is still available but I would expect a MethodMissing error. And as you can guess, this can lead to very complicated situations to debug.
So I got two questions here:
Is it possible to completely remove all dynamic helpers when Rails does a reload during development? Is there some kind of hook I can use?
Is using ActionView::Base.send :include, helpers the right approach for this? Or is there another entry point that I could use (which maybe provides a better reloading approach?)
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.
I have a number of parsers I run with rakes in a project I'm working on. When using a method name that already exists in another rake, and because they both use the same environment, I get a conflict.
Is there a way to limit the scope of rake files within their namespace? I thought that was the whole point of the namespace?
Example:
namespace :test do
task :test_task => :environment do
...
end
def test_method(argument)
...
end
end
namespace :other_test do
task :test_task => :environment do
...
end
def test_method(argument, argument2)
...
end
end
In this case, when running rake test:test_task I'll receive an invalid amount of arguments error. On the other hand, if I define the method within the task itself, I have to keep the method at the top of the rake file in order. This gets kind of confusing and ugly.
Is that just a necessary evil?
Thanks!
I see the namespaces for rake tasks as serving the same purpose as directories in a file system: they're about organization rather than encapsulation. That's why database tasks are in db:, Rails tasks in rails:, etc.
Rake namespaces are not classes so you need to ask yourself what class you're adding test_method to when you define it within a Rake namespace. The answer is Object. So, when you hit your second task, Object already has a test_method method that takes one parameter and Ruby rightly complains.
The best solution is to make your Rake tasks very thin (just like controllers) and put any supporting methods (such as test_method) off in some library file somewhere sensible. A Rake task should usually just do a bit of set up and then call a library method to do the real work (i.e. the same general layout as a controller).
Executive summary: put all the real work and heavy lifting somewhere in your library files and make your Rake tasks thin wrappers for your libraries. This should make your problem go away through proper code organization.
module Anonymous
namespace :test do
# brabra
end
end
self.class.send(:remove_const, :Anonymous)
module Anonymous
namespace :another_test do
# brabra
end
end
self.class.send(:remove_const, :Anonymous)
Module.new do end block needs you self:: before any definition. To avoid this, you need to name the module and remove it.
I've figured out a way to namespace the methods in Rake tasks so same-named methods don't collide.
Wrap each outer namespace in a uniquely-named module.
extend Rake::DSL
Change your methods to class methods (with self.).
I've tested this, and it still allows one rake task to invoke or depend on another rake task that is in a different module.
Example:
module Test
extend Rake::DSL
namespace :test do
task :test_task => :environment do
...
end
def self.test_method(argument)
...
end
end
end
module OtherTest
extend Rake::DSL
namespace :other_test do
task :test_task => :environment do
...
end
def self.test_method(argument, argument2)
...
end
end
end
I want to add a method to the Array class in a rails app. Where should I put this method?
EDIT to be clearer, obviously I put it in a file somewhere, but how do I tell the rails app about where to find it?
One way to do this is to create a file at lib/rails_extensions.rb. Then, add your extensions like so:
class Array
def bring_me_food
# ...
end
def make_tea
# ...
end
end
class Hash
def rub_my_shoulders
# ...
end
end
Then in config/environment.rb, add this:
require 'rails_extensions'
Your mileage with subservient objects may vary.
By default, when you call "require", Rails will look in (from the Rails edge source):
app
app/metal
app/models
app/controllers
app/helpers
app/services
lib
vendor
For simplicity's sake, put the file in lib/, and require it by name in your config/environment.rb, or you can put it in config/initializers/array_extension.rb, and it'll be automatically loaded.
Where I work, we've put all of our extensions to the core Ruby library into a plugin, and stored it in (Rails.root/)vendor/plugins/utilities/lib/core_ext, and then we require the individual extensions in the plugin's init.rb.
Another way to skin this cat: if you say, want to store your core extensions in Rails.root/core_ext, then you can add that path as a load path in your configuration block in environment.rb:
Rails::Initializer.run do |config|
config.load_paths << 'core_ext'
end
Then you can call "require 'array_extension'" from anywhere, and it'll load.
Just put it in a new file, e.g. array_extended.rb
class Array
def my_new_method()
...
end
end
After that you may include this file with require "array_extended.rb".
Be sure you don't override already existing methods as this may break other functionality.