alias_method, alias_method_chain, and self.included - ruby-on-rails

I'm having a little difficulty understanding alias_method/alias_method_chain. I have the following code:
module ActionView::Helpers
module FormHelper
alias_method :form_for_without_cherries, :form_for
def form_for(record, options = {}, &proc)
output = 'with a cherry on top'.html_safe
output.safe_concat form_for_without_cherries(record, options = {}, &proc)
end
end
end
This does exactly what I want to it to - append "with a cherry on top" to the top of any form_for call.
But my understanding is this isn't good code for a few reasons. Firstly, it won't chain in any other overrides of form_for(?) so if I were to write a second form_for method which appended "and yet another cherry on top", it wouldn't show. And secondly, alias_method and alias_method_chain are sorta outdated solutions, and I should be using self.included & sending to the module instead.
But I can't get self.included to call this form_for method - it just keeps calling the parent one. Here's what I'm trying:
module CherryForm
def self.included(base)
base.extend(self)
end
def form_for(record, options = {}, &proc)
output = 'with a cherry on top'.html_safe
output.safe_concat super(record, options = {}, &proc)
end
end
ActionView::Helpers::FormHelper.send(:include, CherryForm)
My above solution works, but I have a suspicion it's the wrong way of doing things. If any ruby veterans could show me a more elegant way of doing this - and/or why the second solution isn't being called - I'd be appreciative.

When you monkey patch something you must redefine the method, because there's no super to inherit from (so you can't use the second code excerpt).
Copying the current method implementation and adding your own is just asking for trouble, so this is where alias_method comes in.
alias_method :form_for_without_cherries, :form_for
It actually creates a clone to the original method, which you can use instead of super. The fact that you can't chain monkey patches is not a bug, it's a feature.
The rails alias_method_chain method was indeed deprecated, but only because it was a poor idea from the start. However alias_method is a pure ruby method, and when used correctly can provide an elegant way of monkey patching your code, that's why I'm pretty sure it's not going away any time soon.

Related

Reload dynamically defined helpers

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?)

Recommended way to use Rails view helpers in a presentation class

I've been researching the 'recommended' way to use Rails view helpers (e.g. link_to, content_tag) in a plain ruby class, such as a presenter. It seems there's very little information on this front and I wanted to get an idea of what the Stack community thought.
So, the options we have are.. (note I'm using Rails 4, and am less concerned about older versions)
Include the required modules manually
This is probably the cleanest way, since only the helpers needed are included. However I have found this method to not work in some cases, as the usual view context provided in plain Rails helpers is configured for the current request. url_for wouldn't know about the current request for example, so the host might not match.
class MyPresenter
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::CaptureHelper
def wrapped_link
content_tag :div, link_to('My link', root_url)
end
end
Use ActionController::Base.helpers
Since Rails 3, ActionController::Base has included a helpers method to access the current view context. I believe the view context provided by this method is configured as it would be in a rails helper, but I might be wrong. There's not really any documentation about this which seems worrying, but it does work quite well in practice.
class MyPresenter
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
protected
def h
ActionController::Base.helpers
end
end
I believe this view context can also be mixed in with include, but the rails view helpers have hundreds of methods and it feels dirty to include them all indiscriminately.
Inject the view context when calling the presenter
Finally, we could just pass the view context to the class when it's initialized (or alternatively in a render method)
class MyPresenter
attr_accessor :context
alias_method :h, :context
def initialize(context)
#context = context
end
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
end
class MyController < ApplicationController
def show
# WARNING - `view_context` in a controller creates an object
#presenter = MyPresenter.new(view_context)
end
end
Personally I tend to lean towards the latter two options, but with no definitive answer from the Rails team (that I've been able to find) I felt a bit unsure. Who better to ask than Stack!
I would go with the mix of the second and third option, something like:
class MyPresenter
def initialize(helpers)
#h = helpers
end
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
private
attr_reader :h
end
Your second option require all your unit tests to be stubbed as ActionController::Base.helpers which maybe isn't a good option and your third option you're using a huge context to access just some methods.
I would really make that dependent on what kind of methods you use. If it's just the basics like content_tag etc. I would go for the ActionController::Base.helpers way. It is also possible to call some helpers directly, e.g. for paths inside models I almost always use something along the lines of Rails.application.routes.url_helpers.comment_path.
For controller-specific stuff the third option might be useful, but personally the "pure" way seems nicer. Draper has an interesting approach too: They save the view_context for the current request and then delegate the calls to h-helpers to it: https://github.com/drapergem/draper/blob/master/lib/draper/view_context.rb
It really is just a matter of preference. I would never include all helpers at once, as you already said. But the second option is quite nice if you want to build the presentation layer yourself without using a gem like Draper or Cells.

Aliasing ExampleGroup

In RSpec I can give alias to examples. For instance, alias_example_to.
Is there any way of aliasing Example Groups? I can use only describe and context. But I want to use, say, feature, scenario...etc. For example,
describe MyObject do
scenario "doing smth with object" do
...
end
end
I found an article on http://benediktdeicke.com/2013/01/custom-rspec-example-groups/.
Is there any other way to alias Example Groups.
As I interpret github, this feature was requested via https://github.com/rspec/rspec-core/issues/493 and is awaiting integration via https://github.com/rspec/rspec-core/pull/870. It is not yet available.
A possible workaround until the feature is released is this:
# spec/support/example_group_aliases.rb
module ExampleGroupAliases
extend ActiveSupport::Concern
included do
class << self
alias_method :simple, :context
end
end
module ClassMethods
def fancy(description, options = {}, &block)
context(description, options.merge(:fancy => true), &block)
end
end
RSpec.configure do |config|
config.include self
end
end
The code shows two ways of defining aliases for the context method. The first (simple) one is using alias_method. The second one (fancy) is defining a new method that then calls the original context method. The last approach allows you to do additional stuff, like adding some more options.

Ruby/Rails: alias_method practices

I'm trying to override Rails' "fields_for" method, which I'm currently doing as follows:
module ActionView::Helpers::FormHelper
include ActionView::Helpers::FormTagHelper
alias_method :original_fields_for, :fields_for
def fields_for(<my arguments>)
# Some extra stuff
# ...
output.safe_concat original_fields_for(<my other arguments>)
end
end
The functionality works just fine, but I'm starting to suspect that my use of alias_method isn't the most elegant. Most especially, if I were to package this functionality into a gem, and there were another gem that overrode fields_for, am I write in thinking either my new fields_for OR the alternate fields_for would be skipped?
Assuming so, what's the correct way to go about slapping in some extra functionality to an existing rails method?
Cheers...
this seems like exactly the situation that alias_method_chain is meant for (although I don't know offhand if it will work on a Module - have only used it on AR::Base)
You'd just do
module ActionView::Helpers::FormHelper
include ActionView::Helpers::FormTagHelper
alias_method_chain :fields_for, :honeypot
def fields_for_with_honeypot(<my arguments>)
# Some extra stuff
# ...
output.safe_concat fields_for_without_honeypot(<my other arguments>)
end
end
interesting idea to do this to fields_for, but it should work.
There is a minor controversy around a_m_c you should be aware of - this post sums it up well http://erniemiller.org/2011/02/03/when-to-use-alias_method_chain/
In this case, I don't think you can use super because you want to monkey-patch form_for without modifying the calling code/views.

Calling Super from an Aliased Method

I am trying to DRY up my code a bit so I am writing a method to defer or delegate certain methods to a different object, but only if it exists. Here is the basic idea: I have Shipment < AbstractShipment which could have a Reroute < AbstractShipment. Either a Shipment or it's Reroute can have a Delivery (or deliveries), but not both.
When I call shipment.deliveries, I want it to check to see if it has a reroute first. If not, then simply call AbstractShipment's deliveries method; if so, delegate the method to the reroute.
I tried this with the simple code below:
module Kernel
private
def this_method
caller[0] =~ /`([^']*)'/ and $1
end
end
class Shipment < AbstractShipment
...
def deferToReroute
if self.reroute.present?
self.reroute.send(this_method)
else
super
end
end
alias_method :isComplete?, :deferToReroute
alias_method :quantityReceived, :deferToReroute
alias_method :receiptDate, :deferToReroute
end
The Kernel.this_method is just a convenience to find out which method was called. However, calling super throws
super: no superclass method `deferToReroute'
I searched a bit and found this link which discusses that this is a bug in Ruby 1.8 but is fixed in 1.9. Unfortunately, I can't upgrade this code to 1.9 yet, so does anyone have any suggestions for workarounds?
Thanks :-)
Edit: After a bit of looking at my code, I realized that I don't actually need to alias all of the methods that I did, I actually only needed to overwrite the deliveries method since the other three actually call it for their calculations. However, I would still love to know y'all's thoughts since I have run into this before.
Rather than using alias_method here, you might be better served by hard-overriding these methods, like so:
class Shipment < AbstractShipment
def isComplete?
return super unless reroute
reroute.isComplete?
end
end
if you find you are doing this 5-10 times per class, you can make it nicer like so:
class Shipment < AbstractShipment
def self.deferred_to_reroute(*method_names)
method_names.each do |method_name|
eval "def #{method_name}; return super unless reroute; reroute.#{method_name}; end"
end
end
deferred_to_reroute :isComplete?, :quantityReceived, :receiptDate
end
Using a straight eval offers good performance characteristics and allows you to have a simple, declarative syntax for what you are doing within your class definition.

Resources