How can I disambiguate my own class from a gem's class - ruby-on-rails

I have a Rails project that I have been working on for a while. Like many Rails projects I have a User class. In one of my controllers I need to access some methods from a gem I am using. The example code from the gem demonstrates using an include to a particular gem module. I'm not going to use the actual gem here because it is not important to the question. I have no control over this gem and I need functionality from the module.
include GemName::Module
The problem is that the Gem includes its own User class directly under the module I have included in my controller. This results in calls to my own User class to methods not defined in the gem's user class failing:
User.working?(:test_user)
NoMethodError (undefined method `working?' for GemName::Module::User:Class):
What I would like to do is be able to include the module in my controller and be able to use my application's User class in that same controller.
I have tested the following workarounds, both of which seem to work, neither of which I am particularly happy with:
Create a new constant to refer to my own user class before including the gem module.
LocalUser = User
include GemName::Module
Do not include the module and explicitly call any classes I may need with the fully namespaced call.
GemName::Module::Class.method
I realize I could namespace my User class but that would involve a lot a refactoring around my codebase and I don't really love the idea anyway. This controller calls classes from the gem about 20 times and my own User class exactly once. The controller never calls the gem's User class. If possible, I'd love to force the call to my own User class to refer to the my non namespaced class explicitly and keep the include to the gem module.
Hopefully there is an elegant solution which will increase my understanding of namespaces in Ruby

Just after posting the question, I thought "What if I simply add :: in front of User when calling my own non-namespaced class?"
Sure enough, it works.
::User.working?(:test_user)
Calls my own User class.
I'm leaving the question and this answer up in case it helps others.

Related

Ruby on Rails extending a gem's class and changing its inheriting class

I have a web app built on Ruby on Rails.
In this app obviously use different Gems.
Iv come to a point where i want to extend some specific gem's classes, and add methods to it.
Now i have a use case where i want to extend a gem's class, but instead of adding methods, i want to change its inherting class
lets take the Impressionist Gem for example:
Iv created a new class in my app - app/models/impression.rb
class Impression < ActiveRecord::Base
establish_connection(ENV[App_LOGS_DB'])
end
i want to change the Inheritance to use a my custom class called LogsBase
class Impression < LogsBase
end
the LogsBase class is definend as follows:
class LogsBase < ActiveRecord::Base
establish_connection ENV['APP_LOGS_DB']
self.abstract_class = true
end
When in try to run the server, the following exception is raised:
/gems/impressionist-1.6.1/lib/impressionist/models/active_record/impression.rb:5:in `<main>': superclass mismatch for class Impression (TypeError)
which from my understanding basically means there is a conflict between the gem's impression class definition and my own extention of that class.
Can anyone please help in finding out a way that i can change the Impression class inherting class, while still perserving the class's behaviour and making my server run properly?
PS: the goal of all this is to write the impressions data into a different database (logs db) rather than the main app db. in order to do that i need to establish a connection to the logs db, but if ill do it within the Impression class directly , it will blow up my pool of DB connections as indicated in the following link:
https://www.thegreatcodeadventure.com/managing-multiple-databases-in-a-single-rails-application/
thats why i need the abstract LogsBase class.
Any help will be appreciated.
Disclaimer: do not do that!
The only way I can think of is a nasty hack, neither reliable, nor robust, that is not compatible with newer versions of your external gem. Since Ruby does not allow to redefine the classes ancestors, you might (but please don’t):
grab the content of original /gems/impressionist-1.6.1/lib/impressionist/models/active_record/impression.rb file.
copy-paste it into your /blah/foo/impression.
make your class be loading impressionist/models/active_record/impression explicitly.
in the very second line of the file unset original class.
Something like this:
require 'impressionist/models/active_record/impression'
Object.send :remove_const, 'Impression'
class Impression < LogsBase
# ORIGINAL CONTENT OF THIS FILE
end
There is no (sensible) way to redefine a base class in ruby. It's possible, but only via weird hacks.
I would suggest taking one of two routes here:
Fork the library, and make the base class configurable (with backwards compatibility).
Don't do this via inheritance. Instead, put establish_connection ENV['SPLACER_LOGS_DB'] into a module, and include it in the class.
I would be inclined to use option 2 for now, as it's a quick/simple workaround that should fit well with the rest of the application.

Accessing helpers from the parent app in an isolated Rails engine

I'm writing a configurable Rails engine. I have an authentication_helper configuration option to define which helper should be called in a before_action in all controllers needing authentication.
The problem is that I don't have access to the parent app's helpers from the engine's controllers. My understanding is that this happens because the engine is isolated.
I have considered using a block instead of a method name, but I'm not sure if that would work, or if I would be able to cleanly access the authorization logic from outside my controllers.
Active Admin, which I have used in the past, has a similar configuration option. I have noticed that their engine is not isolated, so perhaps I'm overrating the importance of engine isolation?
Is there an elegant way to have the benefits of engine isolation while also allowing this kind of customization? Or should I just forego isolation altogether?
EDIT #1
Brad Werth pointed my in the right direction, as this works with a regular controller inheriting from ApplicationController::Base:
module MyBigFancyEngine
class Engine < Rails::Engine
isolate_namespace MyBigFancyEngine
config.to_prepare do
# Make the implementing application's helpers available to the engine.
# This is required for the overriding of engine views and helpers to work correctly.
MyBigFancyEngine::ApplicationController.helper Rails.application.helpers
end
end
end
However, my engine's ApplicationController inherits from RocketPant::Base, which does not provide a helper method. I've tried to use a simple include (which works fine for regular controllers), but that doesn't work either (the controller can't find the helper).
Any ideas?
You can expose the implementing application's helpers available to the engine by including the following code in your engine.rb file:
engine.rb
module MyBigFancyEngine
class Engine < Rails::Engine
isolate_namespace MyBigFancyEngine
config.to_prepare do
# Make the implementing application's helpers available to the engine.
# This is required for the overriding of engine views and helpers to work correctly.
MyBigFancyEngine::ApplicationController.helper Rails.application.helpers
end
end
end
The RailsAdmin Engine is also isolated, but they have the same configuration options as you would like to implement. They have configurable before_filters for both authentication and authorization. Have a look at this.
As far as I can tell, they just subclass the parent controller like this::ApplicationController or instead you can configure one (ref).
For your controller you could just create your own EngineController, that inherits from RocketPant::Base and maybe just create a method there that calls the configured authentication method directly via send on the parent controller.
As the RocketPant::Base Class does not inherit from ApplicationController::Base I guess you have to find some custom way around this and can't go the normal ways for Rails Engines. Maybe you could also try to file an issue to the rocket_pant repo, to add the helper method. As far as I read they soon want to inherit from ApplicationController::Base anyway in 2.0 (ref).
I think you impulse on keeping the isolation is good - because a solution that relies on a method 'just being there' is a pain to debug if s.th. goes wrong in the app using your gem (i.e. typo in method name).
Further if your gem evolves, an some day it won't need the method, in an explicit way it's very convient to give a meainingful error:
You could provide a config method to be called in an initializer:
YourGem.configure do |config|
config.add_callback { MyApp.doIt() }
end
I found this discussion particularly insightful. There are also some interesting ideas in the Rails Engine API under Isolated engine helpers.
The Rails Engine API docs helped me figure out a good solution for url_helpers
You probably moved on by now, but if anyone else needs access to the "parent app" application helpers. You could always just include it explicitly in the application controller in your engine. like so:
include Rails.application.helpers

gem namespaces within rails class

I'm brand new to rails (and ruby) and having a a lot of trouble with accessing different namespaces. Specifically, I can't access the namespace of the flickraw gem from within a controller class:
class ImageSourcesController < ApplicationController
def show
list = flickr.photos.getRecent
...
end
end
Calling this method, I get the response:
undefined local variable or method `flickr' for #<ImageSourcesController:0x00000005006658>
I am using bundler, which I thought ensured that the methods of all gems in the gemfile are required by rails.
EDIT: I'm stupid, turns out I just needed to reset the server!
It is a good idea to create an initializer for flickraw:
# config/initializers/flickraw.rb
FlickRaw.api_key= ENV['FLICKR_API_KEY']
FlickRaw.shared_secret= ENV['FLICKR_API_SECRET']
If you are creating a open source app you may want to use ENV variables to store your API key and shared secret. The dotenv gem is a really nice tool for that.
You also seem to be confused about namespaces in Ruby. Ruby doesn't actually have namespaces in the same way as for example PHP which has a special keyword and namespace accessors.
Ruby has modules which act as both namespaces (grouping classes, constants etc.) and traits. Foo::Bar.create() is an example of accessing a class method on a "namespaced" class.
module Foo
class Bar
def create
end
end
end
Your flickraw example is simply accessing nested properties (which does'nt really have anything to do with namespaces):
flickr.photos.getRecent
Your taking the object flickr (which flickraw creates when we require flickraw) and sending it the message photos which returns a FlickRaw::Flickr::Photos instance.
We then send the message getRecent to flickr.photos

How can you include a mixin to make it available to all of your observers, models, controllers and RSpec?

I want to create a set of methods that are available to my models, controllers, views and RSpec.
Please note I don't want to test these methods in RSpec, I want them available to RSpec to use.
The reason I want to do this is that I have some helpers which override the Rails path_helper methods. Since those path helper methods get used directly in all of these different places I need to mix them in as such.
Hm, it is several ways to achieve your goal, one of them is to include your module with methods direct in ActiveRecord::Base, 'ApplicationController' and Rspec.
Not sure about RSpec (I don't quite sign with it's class/module structure) but you can do something like this in some initializer file:
class ActiveRecord::Base
include MyModuleWithAwesomeMethods
end
class ApplicationController
include MyModuleWithAwesomeMethods
end
etc. for other classes/modules
Hope it will help you.

How do I add a method to a ruby gem without editing the gem source?

I am using the acts_as_taggable_on gem and would like to add a method to one of the gem source files (tag.rb), but I do not want to change the gem source in any way.
I have tried creating my own tag.rb file to in the /app/models directory or in the /lib directory, and then adding the desired method to that file expecting that ruby will merge the two tag.rb files
But when I do I get a NoMethodError: undefined method ...
What am I missing?
I think you're right that reopening the Tag class is the way to go. I wouldn't introduce another level of inheritance unless it really made sense for your code.
I'm not sure, off the top of my head, why reopening the Tag class didn't work. A few thoughts:
1 - When you wrote your own Tag class, did it descend from ActiveRecord::Base? The Tag class in acts as taggable on does, and I could see how neglecting that might mess things up.
2 - If I needed a place to put code that reopened a plugin class for a single method, I'd probably put it in an initializer file (such as config/initializers/tag_patch.rb). Just to keep things clean.
3 - If all else fails and you still can't get the Tag class reopened properly (for whatever reason) there are other metaprogramming techniques you might try to add the method. For example:
Tag.send(:define_method, “method_name”) do
#code for your method
end
Wait, you shouldn't be adding method to the file, but to the class instead. Are you familiar with the concept of reopening the class? You can't add a method just by naming your file same as the one which original class is defined in. Fortunately. :)

Resources