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.
Related
I have tried, seriously. Many questions out there but many developers say "It dont work for me"; I'm one of them -- said to say.
I was reading up on the best way to monkey-patch a rails gem. I've found few but decided to use this method.
I want to monkey-patch the xeroizer gem but rather the invoice.rb model.
# lib/xeroizer/invoice/invoice_url.rb
module Xeroizer
module Invoice
module InvoiceUrl
def invoice_url(id)
#application.http_get(#application.client, "#{url}/#{CGI.escape(id)}/OnlineInvoice")
end
end
end
end
Going with the "this method" link, I assume this should work, but it dosent.
Controller:
include Xeroizer::Invoice::InvoiceUrl
# Invoice.include Xeroizer::Invoice::InvoiceUrl
def some_method
# #xero is in a private method. It's here for short demonstration
#xero = Xeroizer::PrivateApplication.new("MY_CONSUMER_KEY", "MY_SECRET_KEY", "#{Rails.root}/privatekey.pem")
Rails.logger = #xero.Invoice.invoice_url("ad61ea97-b9e9-4a1e-b754-7c19e62f8cd7")
end
undefined method `invoice_url' for Xeroizer::Record::InvoiceModel
How do you add custom methods to a rails gem's class?
Assuming you are trying to monkey-patch Xeroizer::Record::InvoiceModel with Xeroizer::Invoice::InvoiceUrl, you might just do the following right after the first mention of Xeroizer::Record::InvoiceModel (to make Rails to autoload it):
Xeroizer::Record::InvoiceModel.prepend Xeroizer::Invoice::InvoiceUrl
This will override original invoice_url method. The original one still might be called from a prepended using super.
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.
I'm trying to use ruby refinements to apply rails hooks.
I want to avoid monkey patching. When monkey patching it would work as such
ActiveRecord::Base.class_eval do
after_find do
# do something with
my_method
end
def my_method
# something useful
end
end
I've been able to have the class method by doing something like such:
module ActiveRecordRefinements
refine ActiveRecord::Base.singleton_class do
def my_method
#something cool
end
end
end
But I can't run the hook. I tried using self.used(klass) but don't seem to be able to get the syntax just right.
Any help is welcome.
Thanks.
There is a reason you're not using ActiveSupport Callbacks?
Take a look here: http://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html
Pretty simple, I want to use the polymorphic_path method inside a Rails 4 model. Yes I know it's poor separation of concerns. And I know about Rails.application.routes.url_helpers, but polymorphic_path isn't in there.
Try including also PolymorphicRoutes:
include ActionDispatch::Routing::PolymorphicRoutes
include Rails.application.routes.url_helpers
def link
polymorphic_path(self)
end
I know the OP specified Rails 4 but in case someone else ends up here looking for the answer using Rails 5 like I did, here are two ways to access polymorphic_path in a model in Rails 5:
class Something
# The following line is enough, no need for ActionDispatch::Routing::PolymorphicRoutes in Rails 5
include Rails.application.routes.url_helpers
end
Or, if you want to avoid including all methods, just add a private method that wraps the call and you're good to go!
class Something
def do_stuff
polymorphic_path(a_resource)
end
private
def polymorphic_path(resource)
Rails.application.routes.url_helpers.polymorphic_path(resource)
end
end
Notice that the class doesn't need to inherit from ApplicationRecord, both methods work with POROs (Plain-Old Ruby Object).
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.