Overwriting methods in a module - ruby-on-rails

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.

Related

How can I avoid a constant when I want a global object in rails?

I am want to add S3 file storage to my rails 5 application. Since I am using heroku I used their tutorial which says to just create a constant named S3_BUCKET in your config/initializers/aws.rb and you can use that constant everywhere.
The heroku code looks like this:
#config/initializers/aws.rb
S3_BUCKET = Aws::S3::Resource.new.bucket(ENV['S3_BUCKET'])
The problem with this is that I have to override this constant for the specs to work.
I have this alternative solution (which is sadly not working):
#lib/aws_helpers/s3.rb
module AWSHelpers
module S3
class << self
attr_accessor :configuration
def configure
self.configuration ||= Configuration.new
yield(configuration)
end
def bucket
#bucket ||= Aws::S3::Resource.new.bucket(configuration.s3_bucket)
end
end
class Configuration
attr_accessor :s3_bucket, :aws_access_key_id, :aws_secret_access_key_id
end
end
end
#config/initializers/aws.rb
AWSHelpers::S3.configure do |config|
config.s3_bucket = ENV['S3_BUCKET']
config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID']
config.aws_secret_access_key_id = ENV['AWS_SECRET_ACCESS_KEY']
end
What I want to be able to do in a controller is:
AWSHelpers::S3.bucket.object(a_key)
Unfortunately this fails and tells me that bucket method can't return anything because the configuration is nil.
What I know:
the aws initializer gets executed and when I add puts in all the methods I can see the correct output when my server starts via rails s
the controller knows about the service module or it would not even get to the bucket method
the code works when I dump the content of config/initializers/aws.rb into the controller
I really would like to know why the code above is not working. It seems to set up everything correctly and then when I want to use it in the controller suddenly things are as if i'd never called configure.
I am also open to other suggestions on how to do this. Just using the constant is not an option because it has to be changed for the specs to work.
This code might look strange, but it's actually exactly what you'd want in this situation. Keep in mind this situation is a bit irregular, it's for configuring a plugin that has external API dependencies and associated keys that must be populated before the rest of the code works.
It's ugly from an implementation perspective, but from a usability perspective it's nice. You can just do AWSHelpers::S3.configure do |config| as shown in the initializer. All that code is to make those semantics work properly.
This is a cheap trick, but it works.
in config/application.rb:
module YourApp
class Application < Rails::Application
def s3_bucket
#s3_bucket ||= begin
# your stuff
end
end
end
end
Rails.application.s3_bucket.object(a_key)

Patching Module in Ruby

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"

Organizing files in lib directory

I'm trying to extract a portion of my Rails project into my lib directory but I can't work out how to link my files up correctly. My directory structure looks like this:
lib/
eventable/
calendar.rb
helpers.rb
# Rest of rails directories/files
I'm requiring the eventable directory in config/application.rb:
config.autoload_paths += %W(#{config.root}/lib #{config.root}/lib/eventable)
My helpers and calendar rb files:
# helpers.rb
module Eventable
module Helpers
def calendar_for...
Calendar.new...
end
end
end
# calendar.rb
module Eventable
class Calendar
# methods defined here
end
end
I'm then mixing my Eventable::Helpers module in the regular Rails helpers so that I can use calendar_for in my views:
ActionView::Base.send :include, Eventable::Helpers
This last step seems to work fine. However, when I go to a view which is using this helper I get:
uninitialized constant Eventable::Helpers::Calendar
If I change my helper so that it tries to access Eventable::Calendar.new instead then I get:
uninitialized constant Eventable::Calendar
When I had all of these in a single file, it all worked perfectly. So how I can correctly link these files up?
It looks like you need a loader-type file to tell Rails where to find code for the Eventable module.
Try add a lib/eventable.rb with:
module Eventable
autoload :Calendar, 'eventable/calendar'
autoload :Helpers, 'eventable/helpers'
end
You shouldn't need to change your load path if you have the loader file in place.
I had a similar problem. I solved it by changing the way modules are required.
In application.rb
config.autoload_paths += Dir["#{config.root}/lib/"]
Create /lib/eventable.rb with the following code
require "eventable/helpers"
require "eventable/calendar"

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.

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