Monkey-patching database adapter in Rails app - ruby-on-rails

I'm using PosgreSQL adapter to connect to Vertica database which is mostly compatible with PostgreSQL but doesn't support options like client_min_messages (which are still passed to PGconn.connect despite not being present in database.yml). I've made a quick and dirty monkey patch for ActiveRecord::ConnectionAdapters::PostgreSQLAdapter but the problem is that I guess everything in AR in lazy loaded and the original file is read after my patch.
If I add require 'active_record/connection_adapters/postgresql_adapter' at the top of the monkey patch then ActiveRecord tries to establish connection and fails. Is it possible to alter this behavior to make the monkey patch work or should I just write a full-fledged connection adapter?

You can hook your code to railties initialization. Including sample from my gem multi_config:
module <YourModule>
# Railtie subclass for the gem/plugin
class Railtie < Rails::Railtie
# Railtie initializer method
initializer '<your_plugin>.active_record' do
# When active_record is loaded, only then run this.
ActiveSupport.on_load :active_record do
# Hook your code here. For .e.g.
ActiveRecord::Base.send(:include, <YourPluginModule>)
end
end
end
end

Related

How can I extend gem class in Rails 6/Zeitwerk without breaking code reloading?

How do I extend a class that is defined by a gem when I'm using rails 6 / zeitwerk?
I've tried doing it in an initializer using require to load up the class first.
I've tried doing it in an initializer and just referencing the class to let autoloading load it up first.
But both of those approaches break auto-reloading in development mode.
I've tried putting it in lib/ or app/, but that doesn't work because then the class never gets loaded from the gem, since my new file is higher up in the load order.
There is a similar question here, but that one specifically asks how to do this in an initializer. I don't care if it's done in an initializer or not, I just want to figure out how to do it some way.
What is the standard way of doing something like this?
I do have one nasty hack that seems to be working, but I don't like it (update: this doesn't work either. reloading is still broken):
the_gem_root = $LOAD_PATH.grep(/the_gem/).grep(/models/).first
require("#{the_gem_root}/the_gem/some_model")
class SomeModel
def my_extension
...
end
end
I know is late, but this was a real pain and someone could find it helpful, in this example I'll be using a modules folder located on app that will contain custom modules and monkey patches for various gems.
# config/application.rb
...
module MyApp
class Application < Rails::Application
config.load_defaults(6.0)
overrides = "#{Rails.root}/app/overrides"
Rails.autoloaders.main.ignore(overrides)
config.to_prepare do
Dir.glob("#{overrides}/**/*_override.rb").each do |override|
load override
end
end
end
end
Apparently this pattern is called the Override pattern, it will prevent the autoload of your overrides by zeitwerk and each file would be loaded manually at the end of the load.
This pattern is also documented in the Ruby on Rails guide: https://edgeguides.rubyonrails.org/engines.html#overriding-models-and-controllers

ActiveRecord::Base#assign_attributes monkey patch doesn't work after Rails 4 upgrade

I'm upgrading an existing Rails 3 upgrade to Rails 4. This application monkey patches ActiveRecord::Base#assign_attributes and was working fine in Rails 3; now in Rails 4 the monkey patched code never gets called.
I have a config initializer which requires the monkey patched code from the lib directory and I can see that it is run once during the startup initialization process.
This is a simplified version of the monkey patch file from the lib directory.
# encoding: UTF-8
module ActiveRecord
class Base
alias_method :default_assign_attributes, :assign_attributes
def assign_attributes(new_attributes)
# Do some custom stuff here
default_assign_attributes(new_attributes)
end
end
end
This (and many other) methods were split out into concerns in Rails 4. This one went into ActiveRecord::AttributeAssignment. You should be monkey-patching that concern, not ActiveRecord::Base.
That said, if you want to define your own assign_attributes method, you should do so by defining your own concern, not by monkey-patching core classes.

Why do methods defined in an initializer intermittently raise a 'not defined' error which is resolved by restarting the server?

I'm using PaperTrail 4.1 with Rails 4.2.
I have defined several custom methods in an initializer (see: How to add a method to the versions model of Paper_trail?)
#config/initializers/paper_trail.rb
PaperTrail::Rails::Engine.eager_load!
module PaperTrail
class Version < ActiveRecord::Base
scope :scoped, lambda { #selects some records }
def custom_method
#does some stuff
end
end
end
Every so often in development environment I get a method not defined error for methods/ scopes defined in this initializer.
Restarting the server fixes the problem.
Why are these methods being 'lost' to Rails?
Is this an issue that will also present itself in production or other environments?
What steps can I take to find the cause of this issue?
For anyone else arriving here, apparently this is a known issue with PaperTrail
From https://github.com/airblade/paper_trail/pull/492
Now the paper_trail source get's reloaded in the development
environment when saving a file which means the class gets discarded
from the cache and rebuild from the paper_trail sources. The
initializer is not interpreted again since they are one time only, no
module_eval, no abstract class -> exceptions.
And a fix has been included in the latest version of the gem: https://github.com/airblade/paper_trail/pull/557
In essence, it is no longer advised to use an initializer to add custom methods to PaperTrail, and instead to use a model that inherits from PaperTrail (which is a much better fit with AR).
# app/models/paper_trail/version.rb
module PaperTrail
class Version < ActiveRecord::Base
include PaperTrail::VersionConcern
# my custom methods
end
end

Monkey patching a Gem method in Rails 3

I'm willing to use friendly_id gem with Firebird, but I need to override a method that implements a specific SQL function with code related to Firebird.
I've created the file config/initializers/friendly_id_monkey_patch.rb containing the following code:
module FriendlyId
class SlugGenerator
def conflicts
#my_code
end
end
end
However the application keeps executing the original gem's method, ignoring my patch. What could be wrong?

active record helpers defined in ~/.irbrc

I'm really tired of typing my_ar_object.errors.full_messages in my console when i'm testing things...
So, I want to define this:
module ActiveRecord
class Base
def err
errors.full_messages
end
end
end
in my ~/.irbrc so that it is exclusive to script/console.
I don't want to define it in some rails initializer since I don't believe it belongs in the rails project (this is a irb helper)
The problem is, when I do that, this happens:
/.../gems/rails-2.3.5/lib/initializer.rb:437:in `initialize_database':NoMethodError: undefined method `configurations=' for ActiveRecord::Base:Class
Any ideas how I might make this work?
Did you load ActiveRecord in your .irbrc before defining the err method? Try adding
require 'active_record'
or
require 'rubygems'
gem 'activerecord', '2.3.5' # or whatever version you use
before defining the err method.
And another hint: irb looks for an .irbrc file in the current directory and in your home dir. So you could also craft a project-specific .irbrc in your project root directory. This way, you don't have to introduce ActiveRecord to your default irb config since it is a rather hefty dependency.

Resources