How to add optional Rails view helpers to my gem? - ruby-on-rails

I'm working on a gem that does some general string manipulations I'd like to expose as helper methods to rails 4+ apps.
Not all consumers of my gem are rails apps so i'd like to safely expose helper methods to rails apps.
Two questions:
How do I add view helper methods to Rails from a gem and where should it live within the gem directory structure?
What can i do to prevent a blow up when the consumer is NOT a rails app? i.e. the gem can't find rails when it's included
Thanks

In your lib/my_gem.rb, you typically want to do something along these lines:
require 'my_gem/action_methods' if defined? ActionView
And lib/my_gem/action_view_methods.rb would contain all if your methods that require Rails/ActionView.
You can add these helpers to Rails with:
module ActionMethods
# ...
end
ActionView::Base.send :include, ActionMethods
Also see this question, and this one.

The rails way is by creating an engine and as it gets loaded with your gem it gets processed by rails
module MyGem
class Engine < ::Rails::Engine
isolate_namespace MyGem
initializer "my_gem.include_view_helpers" do |app|
ActiveSupport.on_load :action_view do
include MyViewHelper
end
end
end
end
Another way you can go is to not include the helper by default so that consumers don't get unexpected side-effects from your gem. Create the helper as a module and document that it should be added to ApplicationController or any needed controller.

Related

The reason why it's possible to use helper method like current_user, authenticate_user!, and so on without including any module

The title is my question.
devise provide us many useful methods like current_user, authenticate_user!, and so on. I want to know why is it possible to use them without including any module like below.
class ApplicationController < ActionController::Base
before_action :authenticate_user!
end
Those method's definition is here
Somebody please help me!
The answer is devise included its helper methods into ActionController on behalf of you when Rails on_load
# devise/rails.rb
module Devise
class Engine < ::Rails::Engine
# ...
initializer "devise.url_helpers" do
Devise.include_helpers(Devise::Controllers)
end
# ...
end
# devise.rb
# ...
def self.include_helpers(scope)
ActiveSupport.on_load(:action_controller) do
include scope::Helpers if defined?(scope::Helpers)
include scope::UrlHelpers
end
ActiveSupport.on_load(:action_view) do
include scope::UrlHelpers
end
end
# ...
I saw many 3rd gems using on_load to include their methods (or themselves) into Rails core, maybe it's a typical way to do that (allows Rails to lazily load a lot of components and thus making the app boot faster). If you install some gems and you could use their methods on your model/controller/view then those gems did the same thing devise did above.
About those methods current_user, authenticate_user! ... they are dynamic methods devise will generate when it did include scope::Helpers into Rails (see code above and the link).
So one very good thing about rails is the fact that you get a lot of things for free out of the box. One of these things at the top level is autoloading.
So in the case of Devise. When you install Devise and run the generate command, you get a devise.rb file inside of your config/initializers/ folder. This file is always autoloaded by rails on server startup and reload. That's how all these methods are able to be use these devise methods without importing anything.
Read more at rails docs

How do I reference Rails in a gem I'm making?

I'm making a gem for Rails. I need access to the ApplicationController because I'll toy with it. Absolutely nothing online gives information about what to do with gemspec and then somehow manage to get Rails accessible in my gem.
I imagine the goal is eventually to be able to talk to Rails like:
module Rails
module ActionController
#code
end
end
If you are developing a gem exclusively for Rails I strongly recommend you generate the initial scaffold using rails plugin new gem_name. There's a ton of info on developing rails plugins.
The initial structure generated looks like this:
gem_name
gem_name.gemspec
lib/
gem_name.rb
gem_name/
version.rb
engine.rb # if generated using --mountable
The whole rails environment becomes available [edit: after your gem is loaded] so extending ApplicationController can be done like this:
# lib/gem_name.rb
require 'gem_name/controller_extensions'
module GemName
end
# lib/gem_name/controller_extensions.rb
module GemName::ControllerExtensions
# bleh
end
# dummy_application/app/application_controller.rb
class ApplicationController < ActionController::Base
include GemName::ControllerExtensions
end
Look at this question.

Creating gem and trying to reference ApplicationController

I'm creating a gem to create an around_filter on my ApplicationController in all projects that the gem is in. This is my first gem and I'm having a few concerns.
I included rails as a dependency in the gemspec file, but how would I reference anything in rails?
Would I do something like require 'rails' at the top of my file, then do something like
Rails::Path::To::ApplicationController? I understand I could do something like:
Module MyGem
def self.included(klass)
klass.class_eval do
around_filter do |controller, action|
#insert code here
end
end
end
end
Then, in my Rails app I would include it in my Rails ApplicationController, but I don't want to have to include it. Of course, I might have to do some additional config for even that to work, but that is the gist of my crux. How would I reference ApplicationController in my gem? Do I just assume I have Rails since it is a dependency now and I should be able to freely call upon it in my ruby files?

What's the right way to make new methods available to views from a Rails Gem?

If I want to create a new rails gem that adds methods to Rails views what is the right way to do this? Is it to extend ActionView::Base? Would it involve ApplicationHelper in some way?
Many gem authors create a module that defines their view helper methods and then includes them in ActionView::Base.
module MyGem
module ActionViewExtensions
module MyHelpers
def my_view_helper
# ...
end
end
end
end
# You can do this here or in a Railtie
ActionView::Base.send :include, MyGem::ActionViewExtensions::MyHelpers
Railtie method:
https://github.com/mynameisrufus/sorted/blob/master/lib/sorted/railtie.rb
Alternative:
https://github.com/plataformatec/simple_form/blob/master/lib/simple_form/action_view_extensions/form_helper.rb

Rails 3.1: Better way to expose an engine's helper within the client app

I have found a few articles addressing the issue of helpers within an engine not being accessible to the consuming (parent) application. To make sure we are all on the same page, let's say we have this:
module MyEngine
module ImportantHelper
def some_important_helper
...do something important...
end
end
end
If you look at the rails engine documentation in the "Isolated engine's helpers" (L293), it says:
# Sometimes you may want to isolate engine, but use helpers that are defined for it.
# If you want to share just a few specific helpers you can add them to application's
# helpers in ApplicationController:
#
# class ApplicationController < ActionController::Base
# helper MyEngine::SharedEngineHelper
# end
#
# If you want to include all of the engine's helpers, you can use #helpers method on an engine's
# instance:
#
# class ApplicationController < ActionController::Base
# helper MyEngine::Engine.helpers
# end
So if I ask anybody consuming my engine to add this to their application_controller.rb, then they will get access to all my important helper methods:
class ApplicationController < ActionController::Base
helper MyEngine::ImportantHelper
end
This is what I want and it works, but that's kind of a pain, especially if, as is my use case, everything the engine exposes can/should be used anywhere in the consuming app. So I dug around a bit more and found a solution that suggested I do the following:
module MyEngine
class Engine < Rails::Engine
isolate_namespace MyEngine
config.to_prepare do
ApplicationController.helper(ImportantHelper)
end
end
end
Now this is exactly what I want: to add all my ImportantHelper method to the parent app's application helper. However, it doesn't work. Can anybody help me figure out why this more-better solution does not work?
I am running ruby 1.8.7 with rails 3.1.3. Let me know if I missed any important info germane to the issue, and thanks in advance.
You can create an initializer to accomplish this like so:
module MyEngine
class Engine < Rails::Engine
initializer 'my_engine.action_controller' do |app|
ActiveSupport.on_load :action_controller do
helper MyEngine::ImportantHelper
end
end
end
end
I have written two blog posts about creating engines from scratch, and following them everything should work as expected (without additional configurations needed). Maybe you are still interested in:
Rails 3.1 Engines: Part I – The engine
Rails 3.1 Engines: Part II – The gem
Rails 3.1 Engines: Part III – The environment
Update: It's three articles in the meantime, and there's still some info to come. Please give me feedback.
If you want to keep the code in the engine, instead of every implementing application, use this:
module MyEngine
class Engine < Rails::Engine
isolate_namespace MyEngine
config.to_prepare do
MyEngine::ApplicationController.helper Rails.application.helpers
end
end
end
module YourEngine
module Helpers
def a_helper
end
...
end
end
ActionController::Base.send(:helper, YourEngine::Helpers)
Include this code in engine.rb is also be very helpful
config.before_initialize do
ActiveSupport.on_load :action_controller do
helper MyEngine::Engine.helpers
end
end
Basically your engine would look like
module MyEngine
class Engine < Rails::Engine
isolate_namespace MyEngine
# Here comes the code quoted above
end
end

Resources