Monkey patching Rails - ruby-on-rails

I need to monkey-patch one of the Rails core classes, specifically ActionView::Helpers::UrlHelper::ClassMethods.link_to method. As far as I remember there are some events fired when parts of Rails are loaded, how to add handlers for them? Or should I just put the code into initializer?

link_to does not appear to be in ClassMethods. From here.
In config/initializers/url_helper_extensions.rb
module ActionView
module Helpers
module UrlHelper
alias_method :_link_to, :link_to
def link_to
# Your code ...
# Call original method if you want.
_link_to
end
end
end
end

Related

Why can't I override #asset_path from ActionView?

I am working on upgrading an app to Rails 5, and #asset_path now raises if the url is nil. I'm trying to monkey patch that method with a version that will work like Rails 4 so that I can get my tests passing.
I've spent hours on this, and I'm going crazy. For some reason no matter what I do, I can't monkey patch the module. I thought this initializer would work:
module ActionView
module Helpers
module AssetUrlHelper
alias asset_path_raise_on_nil asset_path
def asset_path(source, options = {})
return '' if source.nil?
asset_path_raise_on_nil(source, options)
end
end
end
end
I also tried putting my method in another module and includeing, prepending, and appending it to both ActionView::Helpers::AssetUrlHelper and ActionView::Helpers::AssetTagHelper.
No matter what I do, I can't get my method to be executed. The only way I can alter the method is to bundle open actionview and changing the actual method.
I figured out that it is because #asset_path is simply an alias. I needed to override the method which the alias points at:
module ActionView
module Helpers
module AssetTagHelper
alias_method :path_to_asset_raise_on_nil, :path_to_asset
def path_to_asset(source, options = {})
return '' if source.nil?
path_to_asset_raise_on_nil(source, options)
end
end
end
end

Creating a Gem that adds a rails form helper

I'm creating a gem to add a new helper method for rails forms. My gem is a single file
lib/rails_json_field.rb
that looks like this:
require 'action_view/helpers'
require 'action_view/context'
require 'securerandom'
module ActionView
module Helpers
class FormBuilder
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::JavaScriptHelper
include ActionView::Context
def json_field_tag(method, options = {})
#function code here
end
end
end
end
ActiveSupport.on_load(:action_view) do
include ActionView::Helpers::FormBuilder
end
However when I use the method like so:
= f.json_field_tag(:some_method)
I receive the following error:
ActionView::Template::Error (undefined method `json_field_tag' for #<ActionView::Helpers::FormBuilder:0x007ffa84ab52a8>)
How do I make the method available on ActionView::Helpers::FormBuilder ?
You have defined the following class:
RailsJsonField::ActionView::Helpers::FormBuilder
You meant to monkeypatch the following class:
ActionView::Helpers::FormBuilder
That's why the error message is telling you the method is undefined; you have defined it within a class within your custom module, not within the specified class:
undefined method `json_field_tag' for #<ActionView::Helpers::FormBuilder
It's only defined in RailsJsonField::ActionView::Helpers::FormBuilder, so you get the above error.
If you want to properly monkeypatch the original code then you should look at the original code to ensure your code looks like their code:
module ActionView
module Helpers
class FormBuilder
def json_field_tag(method, options = {})
# function code here
end
end
end
end
It would be better to define this as an initializer in your Rails app, e.g., in config/initializers/json_field_tag.rb. Once you have the code working as a simple patch, then you can focus on developing it into a standalone gem that enhances ActionView.
After searching, I found a different gem that adds a FormBuilder method. I used their repo as a guide to structure my own. For others with this questions, you can view my repo and their repo here respectively:
https://github.com/dyeje/rails_json_field
https://github.com/Brantron/john_hancock

Rails: Make Route Helper Methods Available to PORO

Within a Plain Old Ruby Object (PORO) in my rails app: I have the following method:
def some_method
content_tag(:li, link_to("Do something", somewhere_path(object.id)))
end
First: the object didn't understand the method content_tag, so I added the following which made the object understand that method:
include ActionView::Helpers::TagHelper
Then the object didn't understand link_to so I added the following which made the object understand that method:
include ActionView::Helpers::UrlHelper
Now, it doesn't understand my route: somewhere_path(object.id).
Question: How can I make the PORO in my rails app understand the helpers which generate routes?
Followup Question: Is there an easier way to include all of this functionality into my PORO object? Perhaps there is a way to only include one major module and get all of this functionality (as opposed to perhaps needing to require 3 different modules).
You either have to do what you describe in your self-answer (link to revision I refer to), or inject some context into your POROs. Where context is something which knows all those methods. Something like this:
class ProjectsController
def update
project = Project.find(params[:id])
presenter = Presenters::Project.new(project, context: view_context) # your PORO
# do something with presenter
end
end
And your PORO would look like this:
module Presenters
class Project
attr_reader :presentable, :context
def initialize(presentable, context:)
#presentable = presentable
#context = context
end
def special_link
context.somewhere_path(presentable)
end
end
end
Me, I like neither of them. But sometimes we have to choose a lesser evil.
If anyone happens to know of a current way to get access to all of these methods with one include statement then let me know.
Why, yes. There is a way.
module MyViewCompatibilityPack
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
def url_helpers
Rails.application.routes.url_helpers
end
end
class MyPoro
include MyViewCompatibilityPack
...
end
The issue is that actionview-related methods are not available to POROs.
In order to get all the great stuff from actionview: you need to utilize the view_context keyword. Then: you can simply call upon actionview-related methods from your view_context:
class BuildLink
attr_accessor :blog, :view_context
def initialize(blog, view_context)
#blog = blog
#view_context = view_context
end
def some_method
content_tag(:li, link_to(“Show Blog“, view_context.blog_path(blog)))
end
end
So for example: from your controller you would call upon this PORO like so:
BuildLink.new(#blog, view_context).some_method
For more information, see below references:
Rails doc on view_context
Utilization of view_context via presenter pattern, shown in this article
Railscast which talks through utilizing view_context via presenter pattern

Rails utilize a function in both ActionController and ActiveJob

We have a generic logging function that is in application.rb under our controllers. This function is not found by active job though (I'm assuming as because our email jobs extend ActiveJob::Base vs our controllers that reference ActionController which then references ActionController::Base)
Where would the right place be to put the logging function so we can keep are code as DRY as possible?
So after some talking with others the best course was decided by using the lib folder and creating a module within it.
We created a folder called trackers and a file called tracker.rb in it. Below is a basic snippet of what it looks like
module Trackers
module_function
mattr_accessor :controller
def track_action(event_name, event_params)
event_params["time"] ||= Time.now.utc.to_i
# Controller scope only - this only gets executed if the function is called via a controller vs an ActiveJob
if controller
event_params["controller_name"] ||= controller_name
event_params["action_name"] ||= action_name
end
#Other stuff redacted
end
end
Within the application.rb file we modified the code to include the folder as such:
config.autoload_paths += %W(
#{config.root}/lib/
#{config.root}/lib/trackers/
)
Within a method in a Controller or ActiveJob it is called as such -
Trackers.track_action("eventName", {
"someVar" => "someValue",
})
Another alternative was using a model but I felt this is much more of a lib function - we may turn it into a gem later on.
Hope this helps others in the future.

Where to put `r` alias for method `render` in Rails?

I my views I want to use r alias for method render.
Following code will do this stuff:
module ActionView
# Make alias for :render method
class Base
alias_method :r, :render
end
end
This it pretty handy and nice for me, however I want to ask, what is the best place for this particular snippet? Currently I put this to my app/helpers/application_helper.rb.

Resources