Rails: self.inherited callback and eager class loading (not being very eager...) - ruby-on-rails

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

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 utilize a function in both ActionController and ActiveJob

We have a generic logging function that is in application.rb under our controllers. This function is not found by active job though (I'm assuming as because our email jobs extend ActiveJob::Base vs our controllers that reference ActionController which then references ActionController::Base)
Where would the right place be to put the logging function so we can keep are code as DRY as possible?
So after some talking with others the best course was decided by using the lib folder and creating a module within it.
We created a folder called trackers and a file called tracker.rb in it. Below is a basic snippet of what it looks like
module Trackers
module_function
mattr_accessor :controller
def track_action(event_name, event_params)
event_params["time"] ||= Time.now.utc.to_i
# Controller scope only - this only gets executed if the function is called via a controller vs an ActiveJob
if controller
event_params["controller_name"] ||= controller_name
event_params["action_name"] ||= action_name
end
#Other stuff redacted
end
end
Within the application.rb file we modified the code to include the folder as such:
config.autoload_paths += %W(
#{config.root}/lib/
#{config.root}/lib/trackers/
)
Within a method in a Controller or ActiveJob it is called as such -
Trackers.track_action("eventName", {
"someVar" => "someValue",
})
Another alternative was using a model but I felt this is much more of a lib function - we may turn it into a gem later on.
Hope this helps others in the future.

How to extend a mountable engine's model inside another mountable engine with development environment reloading

Using Rails 3.2.2 and Ruby 1.9.2.
I have a rails mountable engine EngineA that declares a User class inheriting form ActiveRecord::Base. I have another engine EngineB that wants to inject functionality into EngineA::User. Right now what I have done is shown below:
Method 1:
#EngineA app/models/engine_a/user.rb
module EngineA
class User < ActiveRecord::Base
has_attached_file :avatar
has_many :somethings
end
end
#EngineB lib/engine_b/user.rb
module EngineB
module User
def self.extended obj
obj.class_eval do
has_many :something_elses
end
end
end
end
EngineA::User.extend EngineB::User
This gives me an uninitialized constant EngineA::User error. Even when I require that specific file I run into the problem of EngineA needing paperclip so that has_attached_file is understood. That road ended when I realized I would have to know and require the dependencies for EngineA inside EngineB.
Method 2:
I used the same code as before except I removed the last line EngineA::User.extend EngineB::User from the EngineB user.rb file. I then moved that call to an initializer inside EngineB.
#EngineB config/initializers/my_mixin.rb
EngineA::User.extend EngineB::User
This worked perfectly!!! Except in development mode when I would change code and the models would refresh. The only thing that was refreshed was the EngineA::User and not the mixin that I had put as an initializer. So once I changed code, I lost all of my extended functionality.
I'm not even positive this is the most 'efficient' way to do this... any help would be greatly appreciated. Thanks in advance.
According to the configuration documentation, you can use an ActionDispatch callback to load items. These callbacks will run when at every request if cache_classes is set to false, like in development mode.
Inside of your EngineB.rb file, you might try something like this:
if Rails.env.development?
ActionDispatch::Callbacks.to_prepare do
load "#{File.expand_path(File.dirname(__FILE__))}/../config/initializers/my_mixin.rb"
end
end

Including methods to a controller from a plugin

Using Rails 2.3.11, I'm creating a plugin for Redmine that add methods to ApplicationController.
I've created the following module, in the plugin :
module ApplicationControllerPatch
def self.included(base) # :nodoc:
base.class_eval do
rescue_from AnException, :with => :rescue_method
def rescue_method(exception)
...
end
end
end
end
Now, if I include this module directly into the application_controller.rb file, like this:
class ApplicationController < ActionController::Base
include ApplicationControllerPatch
...
end
Everything works just fine, however I would like to avoid editing the core source by including this module from the plugin itself.
So far, if I do:
ApplicationController.send(:include, ApplicationControllerPatch)
directly from this module file (located in the plugin folder). This will load properly for the request and then it gets overwritten by the controller (I guess).
What's is the way to accomplish this ?
A common pattern is to use Dispatcher.to_prepare inside your plugin's init.rb. This is required because in development mode (or generally if config.cache_classes = false) Rails reloads all classes right before each request to pick up changes without the need to completely restart the application server each time.
This however means the you have to apply your patch again after the class got reloaded as Rails can't know which modules got injected later on. Using Dispatcher.to_prepare you can achieve exactly that. The code defined in the block is executed once in production mode and before each request in development mode which makes it the premier place to monkey patch core classes.
The upside of this approach is that you can have your plugins self-contained and do not need to change anything in the surrounding application.
Put this inside your init.rb, e.g. vendor/plugins/my_plugin/init.rb
require 'redmine'
# Patches to the Redmine core.
require 'dispatcher'
Dispatcher.to_prepare do
ApplicationController.send(:include, MyPlugin::ApplicationControllerPatch) unless ApplicationController.include?(RedmineSpentTimeColumn::Patches::IssuePatch)
end
Redmine::Plugin.register :my_plugin do
name 'My Plugin'
[...]
end
Your patch should always be namespaced inside a module named after your plugin to not run into issues with multiple plugins defining the same module names. Then put the patch into lib/my_plugin/application_controller_patch.rb. That way, it will be picked up automatically by the Rails Autoloader.
Put this into vendor/plugins/my_plugin/lib/my_plugin/application_controller_patch.rb
module MyPlugin
module ApplicationControllerPatch
def self.included(base) # :nodoc:
base.class_eval do
rescue_from AnException, :with => :rescue_method
def rescue_method(exception)
[...]
end
end
end
end
end
This kind of problem occurs only in dev because the classes are reloaded but not gems.
So add your send method in a config.to_prepare block within config/environments/development.rb
Read Rails doc concerning initialization process for further details.

adding a method to built-in class in rails app

I want to add a method to the Array class in a rails app. Where should I put this method?
EDIT to be clearer, obviously I put it in a file somewhere, but how do I tell the rails app about where to find it?
One way to do this is to create a file at lib/rails_extensions.rb. Then, add your extensions like so:
class Array
def bring_me_food
# ...
end
def make_tea
# ...
end
end
class Hash
def rub_my_shoulders
# ...
end
end
Then in config/environment.rb, add this:
require 'rails_extensions'
Your mileage with subservient objects may vary.
By default, when you call "require", Rails will look in (from the Rails edge source):
app
app/metal
app/models
app/controllers
app/helpers
app/services
lib
vendor
For simplicity's sake, put the file in lib/, and require it by name in your config/environment.rb, or you can put it in config/initializers/array_extension.rb, and it'll be automatically loaded.
Where I work, we've put all of our extensions to the core Ruby library into a plugin, and stored it in (Rails.root/)vendor/plugins/utilities/lib/core_ext, and then we require the individual extensions in the plugin's init.rb.
Another way to skin this cat: if you say, want to store your core extensions in Rails.root/core_ext, then you can add that path as a load path in your configuration block in environment.rb:
Rails::Initializer.run do |config|
config.load_paths << 'core_ext'
end
Then you can call "require 'array_extension'" from anywhere, and it'll load.
Just put it in a new file, e.g. array_extended.rb
class Array
def my_new_method()
...
end
end
After that you may include this file with require "array_extended.rb".
Be sure you don't override already existing methods as this may break other functionality.

Resources