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
Related
On this project setup :
RoR : 6.0.3.4
Ruby: 2.7.1p83
I have this bit of code serving as a Nokogiri helper - within app > helpers > nepper > helpers > nokogiri.rb (a bit redundant fo'sure) :
# frozen_string_literal: true
module Nepper
module Helpers
# Dedicated to Nokogiri utilities
module Nokogiri
# HTML::Document extension
class ::Nokogiri::HTML::Document
def has?(css_selector)
# css_selector (symbole): const name
css(css_selectors.const_get(css_selector)).present?
end
def text_at(css_selector)
# css_selector (symbole): const name
elt = css(css_selectors.const_get(css_selector))
# raise TypeError 'No text on node' unless elt.text.present?
elt.text
end
def href_at(css_selector)
# css_selector (symbole): const name
elt = css(css_selectors.const_get(css_selector)).first
# raise TypeError 'Not a link' unless elt[:href].present?
elt[:href]
end
end
# css selectors constants shortcut
def css_selectors
Nepper::Constants::CssSelector
end
end
end
end
and within a model declaration, something looking like this
class Card
include Mongoid::Document
include Nepper::Helpers::Nokogiri
# [...]
end
My issue is that, within rails console, I get undefined method for Nokogiri::HTML::Document error for css_selectors whenever an instance of the class is using any of the previous added methods.
More troublesome : It worked just fine yesterday right before pushing the code.
I could put the method within the Nokogiri::HTML::Document method implementation, but I want to keep this bit of code accessible whenether a file includes it, without having to use a Nokogiri instance nor duplicating the code.
So ... What's going on with this weird load behaviour ?
I don't get why the Nokogiri::HTML::Document instances don't access the module method anymore ... both should exist once this code is included isn't it ?
Edit : Btw I had the same issue with TypeError being errored as undefined - for why I had to comment those raise error parts (also weird). This is as if some fo the Main object modules are not loaded prior to those methods calls.
Why isn't the Ruby interpreter throwing a NameError in this instance here?
class OrangeTurtle
self.table_name = 'turtles'
end
Filename: orange_turtles.rb
This answer might sound like a cop out, but it doesn't throw an error because Ruby doesn't care even the slightest what your filenames are called.
e.g. in file asdfasdf.no_rb_ending_here we can have
#!/usr/bin/env ruby
module Something
class Test
def test
puts 'test'
end
end
end
class SomethingElse
def otherThings
puts 'haha'
end
end
Then to make things even weirder, I can have a separate file that modifies (monkey patches) the classes defined in that file.
in more_stuff.rb
#!/usr/bin/env ruby
require_relative 'asdfasdf.no_rb_ending_here'
module Something
class Test
def test2
test()
puts '2'
end
end
end
class SomethingElse
def moreThings
otherThings()
puts 'MOAR'
end
end
Something::Test.new.test2()
# test
# 2
SomethingElse.new.moreThings()
# haha
# MOAR
Ruby is pretty cool - you don't get errors for things that don't NEED to cause an error.
The name error, or uninitialized constant error only appears in Rails. The reason for that is, that active record (which is also a general design pattern) is mapping the tables in the database with the models (or with objects in general).
Active Record can only make that connection via the naming conventions for files and the classes.
As mentioned in the other answer, pure ruby doesn't need to comply with these conventions. However, it is a general rule to name the files like the classes they contain to have better organised code.
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.
In Rails, how do you use a specific method from a module. For eg,
# ./app/controllers/my_controller.rb
class MyController < ApplicationController
include MyModule
def action
MyModule.a_method
end
private
def a_method
...
end
end
# ------------------------------------------------ #
# ./app/helpers/my_module.rb
module MyModule
def a_method
...
end
end
MyController includes MyModule. And in action ,I want to use MyModule.a_method (Please note I also have a private a_method in MyController and I don't want to use this.)
Things I've tried :
1) Defining the method in the module as self.
def self.a_method
end
2) Using the :: notation in controller (MyModule::a_method)
The error that I keep getting is
Undefined method:a_method for MyModule:module
For now, I've resorted to using a different name for the modules method. But I'd like to know how to namespace the function with either the Module:: or Module. notation
[UPDATE - 11/24/2014]
adding file structure in code, since Rails heavily relies on convention.
So I am not really sure what you are trying to accomplish with your module but a quick solution to get it working is below.
Move my_module.rb out of helpers and into lib/my_module.rb. The helpers directory is for methods that you use in your views. The convention is to utilize helpers that are namespaced after their respective controller or the application_helper.rb for global methods for your views. Not sure if that's what you are trying to accomplish with your module but wanted to throw that out there.
Create an initializer (you can all it whatever) in config/initializers/custom_modules.rb and add require 'my_module'
Update the a_method back to be self.a_method
You can now call MyModule.a_method in your app
Don't forget to restart your server for changes to lib/my_module.rb to take effect.
Also, a lot of people reference this post by Yehuda Katz as guidance on where to store code for your app. Thought it might be a helpful reference.
if you include MyModule into MyController, all the "instance methods" of the first will be mixed-in into the 2nd.
So if you only want to call MyModule.a_method, no need to include your module.
Then you'd want to require (or better autoload) your module before using it. To do so place it in controllers/concerns/my_module.rb, rails (4 at least) should autoload it, otherwise require its file in an intializer
# my_module.rb
module MyModule
def self.a_method
...
end
end
should work, but doing
# my_module.rb
module MyModule
extend self
def a_method
...
end
end
is more clean to me. You'd like to have a look to rails active support concern to understand the "rails way" on this topic.
Setting
In my gem there is a Component base class:
module Component
class Base
def self.inherited(component_class)
Component.class_eval do
define_method(component_class.to_s.underscore) do |*args, &block|
component_class.new(*args, &block)
end
end
end
end
end
For every class inheriting from this base class (e.g. FancyComponent < Component::Base), a helper should be defined in the Component module (e.g. fancy_component).
This does work for any components delivered with my gem, i.e. Component.instance_methods.include? :fancy_component #=> true
Now Rails comes into play
I want users of my gem to be able to add components. These are stored in app/components.
This folder is included in all of the following:
MyApp::Application.config.load_paths
MyApp::Application.config.autoload_paths
MyApp::Application.config.eager_load_paths
A new component UserComponent < Component::Baseis stored in app/components/user_component.rb.
The Problem
If I launch the rails console, the situation is as follows:
Loading development environment (Rails 3.0.4, ruby-1.9.2-p0)
Component.instance_methods.include? :fancy_component #=> true
Component.instance_methods.include? :user_component #=> false
UserComponent #=> UserComponent
Component.instance_methods.include? :user_component #=> true
So the helper method is not available until the component class is somehow accessed.
So how to force eager loading of that class so that inherited is executed?
Your idea is nice but I'd greatly advise you against implementing something like this, `cos this would usually be done by pre-loading the models before Rails takes notice of them and this could lead to hard to figure loading issues in your app (like classes that should have been re-loaded but were not).
Here's a basic example, of the ways for you to implement this feature would be, at your "root" gem file (if your gem is named "my_component", the "lib/my_component.rb" file) do something like this:
require 'component/base/component'
# require here all other classes necessary for your gem
rb_files = File.join( Rails.root, 'app', 'components', '**', '*.rb' )
Dir.glob( rb_files ).each do |file|
require file.gsub('.rb')
end
With this you'd load all files under "app/components" but then Rails would not reload these objects, as they were not required by Rails, but by your own gem. If you don't mind not being able to change these files, this might be ok, but then you could have issues in the development environment (or any environment that has "cache_classes" set to false).
This is a bug in rails (but wontfix), see also this ticket here:
https://github.com/rails/rails/issues/3364