Patching Module in Ruby - ruby-on-rails

I'm trying to add the following code that I found online as a workaround for a problem with combining MongoDB and Backbone.js in Rails, but I actually don't know Ruby/Rails that well because I'm learning all three at once.
Currently, I've just created a file in lib/ called mongoid.rb with the following content:
module Mongoid
module BackboneSerialization
extend ActiveSupport::Concern
module InstanceMethods
def serializable_hash(options = nil)
persisted? ? super.merge('id' => _id) : super
end
end
end
end
Assuming that this code is correct, is there anything else I have to be aware of to make this work. All I'm doing now is adding this code and then starting my server but that doesn't seem to solve the problem. Is there a specific place I need to store it- like lib/mongoid/backbone_serialization/instance_methods? Or do I need to include this in certain files? If so, do I include just Mongoid or submodules?

config/environment.rb,
after require File.expand_path('../application', __FILE__)
and before APPNAME::Application.initialize!
add require "mongoid"

Related

Accessing helpers and models from rails engine initializer

I'm trying to make a Ruby on Rails engine, and I want the initializer to be able to have access to the helpers and models.
I'll write below an example, part of the code, and the error that I have. It may not be the recommended way, because I can see that in some cases I'm repeating myself, but it's the first engine I make.
file lib/my_engine/engine.rb
module MyEngine
require 'my_engine/functions'
class Engine < ::Rails::Engine
isolate_namespace MyEngine
config.autoload_paths += %W( #{config.root}/lib )
end
class GlobalVars
attr_accessor :foo
def initialize
#foo = MyEngine::Functions.new
end
end
class << self
mattr_accessor :GLOBAL
mattr_accessor :USER_CONFIG
self.GLOBAL = MyEngine::GlobalVars.new
# add default values of more config vars here
self.USER_CONFIG = 'default config'
end
def self.setup(&block)
yield self
end
end
file lib/my_engine/functions.rb
module MyEngine
require '../../app/helpers/my_engine/options_helper'
class Functions
include MyEngine::OptionsHelper
attr_accessor :some_link
def initialize
#some_link = get_option('dummy')
end
end
end
There is also a controller named OptionsController in app/controllers/my_engine, and OptionsHelper in app/helpers/my_engine/options_helper.rb:
module MyEngine
module OptionsHelper
def get_option(name)
MyEngine::Option.new
end
end
end
When I try to run the dummy application, this error occurs:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
If I change to just Option.new, I have this error:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::OptionsHelper::Option (NameError)
For ::MyEngine::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
For ::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant Option (NameError)
The dummy application has nothing in it. All helpers and models defined above are in the engine.
Before this, I had other errors because it couldn't access the helper, or the Functions class. I had to add require and include to make it work even if they are placed in the same directory. Also, to work, I had to move GlobalVars from its own file inside engine.rb.
Can somebody show me what I'm doing wrong?
After I used required for every class, I ended with ActiveRecord::ConnectionNotEstablished, and it seems that not everything is loaded and available at that point when the GLOBAL object is created.
So I moved the code that was using the models in a separate init method. Then, I added an after initialize event:
config.after_initialize do
MyEngine.GLOBAL.init
end
I see a possible problem: because you are inside module MyEngine it might be possible that actually rails is looking for MyEngine::MyEngine::Option, so I see two approaches:
just write Option: this will look for MyEngine::Option
write ::MyEngine::Option this will look in the global namespace and find MyEngine::Option
Secondly, if that does not help, even though your path seems correct, but you can always explicitly require "my_engine/option" at the top of the file. I am not entirely sure the autoloading in an engine works in quite the same way, and I tend to, in my engine file, require almost everything (to make sure it works).
In my engine.rb I do
require_relative '../../app/models/my_engine/option'
maybe this will help, but it is not a nice solution.

Overwriting methods in a module

I am using a gem in my rails app, and there is a method that I would like to override. The gam is authlogic, and the specific method I was to override is find_by_smart_case_login_field(login).
I made a file in lib/modules with the following code:
# lib/modules/login.rb
module Authlogic
module ActsAsAuthentic
module Login
module Config
def find_by_smart_case_login_field(login)
login = login.downcase unless validates_uniqueness_of_login_field_options[:case_sensitive]
if login_field
where({ login_field.to_sym => login })
else
where({ email_field.to_sym => login })
end
end
end
end
end
end
But this didn't do anything. Does anyone know how to overwrite the above method?
Well, you are monkey patching a gem. Not bad, just don't abuse it:)
Two things you need to do before making your monkey patching works.
Add /lib to auto load path otherwise Rails don't know it.
In config/application.rb, find the autoload_path line, change it to
config.autoload_paths += %W(#{config.root}/extras #{config.root}/lib)
Require your custom module at app loading.
In config/initializers, add a custom file say application.rb, then add the following line
require 'modules/login.rb'
# Pay attention: No "lib/" before the file path
Now, profit!
As to module path, it doesn't matter as long as your module nesting is correct in the file.
I'm going out on a limb here, but my guess would be that you'd have to name the file something like
lib/authlogic/acts_as_authentic/login/config.rb
In other words, I believe the path has to map to the module structure.

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.

Can't access model from inside ActionController method added by a Rails engine

I am developing a Rails engine to be packaged as a gem. In my engine's main module file, I have:
module Auditor
require 'engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
require 'application_controller'
end
module ActionController
module Auditor
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def is_audited
include ActionController::Auditor::InstanceMethods
before_filter :audit_request
end
end
module InstanceMethods
def audit_request
a = AuditorLog.new
a.save!
end
end
end
end
ActionController::Base.send(:include, ActionController::Auditor)
where AuditorLog is a model also provided by the engine. (My intent is to have "is_audited" added to the controllers in an application using this engine which will cause audit logging of the details of the request.)
The problem I have is that when this code gets called from an application where the engine is being used, the AuditorLog model isn't accessible. It looks like Ruby thinks it should be a class in ActionController:
NameError (uninitialized constant
ActionController::Auditor::InstanceMethods::AuditorLog)
rather than a model from my engine.
Can anyone point me in the right direction? This is my first time creating an engine and attempting to package it as a gem; I've searched for examples of this and haven't had much luck. My approach to adding this capability to the ActionController class was based on what mobile_fu does, so please let me know if I'm going about this all wrong.
Use ::AuditorLog to access the ActiveRecord class (unless you have it in a module or namespace, in which case you'll need to include the module name).

How can I extend ActiveRecord from app/modules?

I have several different acts_as_... custom class methods I'd like to use in my app. I would like the code for those methods to be in files in the app/modules directory.
I have been unable to get this working.
For instance, I have a file: app/modules/acts_as_lockable
module ActsAsLockable
def acts_as_lockable
before_create :set_lock
include InstanceMethods
end
module InstanceMethods
protected
def set_lock
now = Time.now.to_s
self.lock = Digest::SHA1.hexdigest(now)
end
end
end
ActiveRecord::Base.extend ActsAsLockable
And in application.rb
config.autoload_paths += %W(#{config.root}/app/modules)
When I try to load up a model that calls acts_as_lockable I get the following error:
NameError: undefined local variable or
method `acts_as_lockable'
My guess is that I shouldn't be autoloading the modules folder because ActiveRecord has already been loaded when I extend it? Is there another way to do this? I would like to be able to alter the file during development without restarting my server but that's more of a want that a need.
I think you're thinking about this in the wrong way.
You are adding this module to the load path,
but it will only load if you either say;
require 'acts_as_lockable'
or
ActsAsLockable
I'd suggest you never really want to say either of these inside your code.
The correct paradigm you're looking for is an "initializer".
I suggest you create a file called "config/initializers/acts_as_lockable.rb"
In this file you can either include the whole code,
or just include a require 'acts_as_lockable'
Normally I keep things like this inside the libs directory
ensure lib is in the load path
** config/application.rb **
config.autoload_paths += %W(#{config.root}/lib)
** lib/acts_as_lockable.rb **
module ActsAsLockable
def acts_as_lockable
before_create :set_lock
include InstanceMethods
end
module InstanceMethods
protected
def set_lock
now = Time.now.to_s
self.lock = Digest::SHA1.hexdigest(now)
end
end
end
then in the initializer
** config/initializers/acts_as_lockable.rb **
require 'acts_as_lockable'
ActiveRecord::Base.extend ActsAsLockable
The problem is that ruby autoload mechanism is a lazy process: When a constant like ActsAsLockable is used within your code, it looks for a file called acts_as_lockable.rb within the autoload_paths. As You never actually use ActsAsLockable, the file never gets loaded. You could do (although not tremendously beautiful):
ActsAsLockable
class MyModel < ActiveRecord::Base
acts_as_lockable
...
end
I think the acts_as_* pattern is ment to be used be plugins and gems to easily integrate functionality into your code. Plugins and gems are supposed to be in a final state when you integrate them into your project so you would not need the reloading functionality for the development mode.
I hope this helps.

Resources