Safe and best way to monkey patch a rails gem - ruby-on-rails

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.

Related

How to detect if ruby code is invoked via Ruby on Rails

I wrote a small .rb tool that used the "blank?" method. I want my program to continue to work if invoked directly by ruby. I Monkey Patched Object with code below but I don't want to monkey patch when running under Rails. What can I do?
class Object
def blank?
respond_to?(:empty?) ? (respond_to?(:strip) ? strip.empty? : !!empty?) : !self
end
end
The first thing to keep in mind is that monkey-patching a class directly — that is, opening a class to define a new method — is discouraged. It works, but it's not very flexible and it's considered a code smell.
A more sensible approach to monkey-patching is to define your methods in a mixin and then including it in a class.
This also allows you to conditionally include the mixin. For example, a common requirement in Ruby Gems is to only implement or define something if another library is (already) loaded. A common way to do this is to check if a constant from that library is defined. For example, in your case you could do this:
module PresenceExtensions
def blank?
respond_to?(:empty?) ? (respond_to?(:strip) ? strip.empty? : !!empty?) : !self
end
end
unless Module.const_defined?("Rails") || Object.method_defined?(:blank?)
Object.include PresenceExtensions
end
Another common technique is to try to load a gem and then add your alternative monkey-patch only if the gem is not available:
begin
require "active_support/core_ext/object/blank"
rescue LoadError
Object.include PresenceExtensions
end
This technique has the advantage that will tell you immediately if the gem is not available, so that you don't have to worry about load order.
Rails by default should not load a file at runtime unless it is expected to through some kind of configuration either by default or an initializer etc. If this class definition just sits inside your lib/monkey.rb for example, Rails won't auto-load it unless you tell it to.
You can test this out in your rails console if you are using pry.
Just do:
rails c
# inside your console:
show-method Object.blank?
# this should show you the actual method definition which should be somthing
# like:
From: /Users/myself/.rvm/gems/ruby-2.5.1/gems/activesupport-4.2.10/lib/active_support/core_ext/object/blank.rb # line 16:
Owner: Object
Visibility: public
Number of lines: 3
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
But if rails had loaded your lib file, you would see instead that definition which you can force in the console with require
require './lib/monkey.rb'
show-method Object.blank?
From: /Users/myself/some/rails/project/lib/monkey.rb # line 2:
Owner: Object
Visibility: public
Number of lines: 4
def blank?
puts "this is a monkey patch"
respond_to?(:empty?) ? (respond_to?(:strip) ? strip.empty? : !!empty?) : !self
end

Rails 4 + append_view_path

I want to use dynamic path in my controller in rails gem.
I've added to
module MyGem
class FooController < Config.controller
before_action ->{ append_view_path "app/views/my_gem/#{wizard_name}" }
...
and in views I need to specify path like
app/views/my_gem/#{wizard_name}/my_gem/foo/some.erb
is in Rails some way, to cut the relative path of gem namespace, and get lookup path like?
app/views/my_gem/#{wizard_name}/some.erb
# or
foo/bar/some.erb
Thank you!
UPD:
I understand, that there is way with disabling isolate_namespace in Engine, but I believe, that it is not best and only option.
UPD2: For Rails4 this idea was very useful https://coderwall.com/p/p_yelg/render-view-outside-of-a-controller-in-rails-4
The Devise gem has a way of adding views lookup path by overriding the _prefixes method:
class DeviseController < Devise.parent_controller.constantize
include Devise::Controllers::ScopedViews
# Override prefixes to consider the scoped view.
# Notice we need to check for the request due to a bug in
# Action Controller tests that forces _prefixes to be
# loaded before even having a request object.
#
# This method should be public as it is is in ActionPack
# itself. Changing its visibility may break other gems.
def _prefixes #:nodoc:
#_prefixes ||= if self.class.scoped_views? && request && devise_mapping
["#{devise_mapping.scoped_path}/#{controller_name}"] + super
else
super
end
end
end
Can this be applied to your use case?
See source:
https://github.com/plataformatec/devise/blob/master/app/controllers/devise_controller.rb
Usually, you should only override the partial views or the functions from that gem, do not load from the gem lib like this, because when deploying to the real server it will raise many troubles for you to debug and improve.
You could make your how render method that reads an arbitrary file, interpret the ERB and render it as an HTML page for instance.
ERB.new(File.read(Rails.root.join('foo', bar', 'some.erb'))).result(binding)
By passing binding, the template will have access to the all the variables in the current context.
See ERB docs for more details: http://apidock.com/ruby/ERB
Assuming your gem is an engine, you should be able to simply call render :some in the engine. If the app has a view called <gem_name>/<controller_name>/some.html.erb it will be used.
Also, you can provide a version of that view in your gem that will be used if the app does not yet provide one.
If you need the wizard_name to also be looked up, I think the best way to do that would be to move that portion of the view path to the to the end where you are calling render.
So in your gem's controller you would write render "#{wizard_name}/some" and it would look for that view both in our app's app/views/<gem_name>/<controller_name>/<wizard_name>/some.html.erb and in your gem's app/views/<controller_name>/<wizard_name>/some.html.erb.

Helper methods for models in Rails

Is there a proper place for helper methods for models in Rails? There are helper methods for controllers and views, but I'm not sure where the best place to put model helper methods. Aside from adding a method to ActiveRecord::Base, which I'd prefer not to.
UPDATE: It seems Concerns make a lot of sense. Here's an example of what I want. Certain models can never be deleted, so I add a callback that always throws an exception:
before_destroy :nope
def nope
raise 'Deleting not allowed'
end
With concerns, I could do something like this?
class MyModel < ActiveRecord::Base
include Undeletable
end
module Undeletable
extend ActiveSupport::Concern
included do
before_destroy :nope
end
def nope
raise 'Deleting not allowed'
end
end
Is this the Rails way of doing this?
If you want to use a helper_method my_helper_method inside a model, you can write
ApplicationController.helpers.my_helper_method
If you need a bit more flexibility, for example if you also need to override some methods, you can do this:
class HelperProxy < ActionView::Base
include ApplicationController.master_helper_module
def current_user
#let helpers act like we're a guest
nil
end
def self.instance
#instance ||= new
end
end
and then use with
HelperProxy.instance.my_helper_method
If you have strong nerves, you can also try to include the ApplicationController.master_helper_module directly into your model.
via : makandracards's post.
For your reference: http://railscasts.com/episodes/132-helpers-outside-views
If what you are asking is where to put code that is shared across multiple models in rails 4.2, then the standard answer has to be to use Concerns: How to use concerns in Rails 4
However, there are some good arguments (e.g. this) to just using standard rails module includes, and extends as marek-lipka suggests.
I would strongly recommend NOT using ApplicationController helper methods in a model, as you'll be importing a lot unnecessary baggage along with it. Doing so is usually a bad smell in my opinion, as it means you are not separating the MVC elements, and there is too much interdependency in your app.
If you need to modify a model object by adding a method that is just used within a view, then have a look at decorators. For example https://github.com/drapergem/draper

How can I access polymorphic_path inside a model in Rails 4?

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

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.

Resources