In the application I'm creating I use a gem. This gem has a Module with a method that is called by the gem when something changes. What I need to do is extend the functionality of this method. I cannot change the name of this method and I am not able to call a different method instead.
I'm talking about the rebuild method defined here:
https://github.com/the-teacher/the_sortable_tree/blob/master/app/controllers/the_sortable_tree_controller.rb
How can I add functionality to this method without touching the source?
You need to include the module with the function name which you want to override in the controller and then you can write the function with the same name and call super when you are done with your work.
class A
include TheSortableTreeController::Rebuild
def rebuild
# do something here
super
end
This way you will be able to perform yours as well as the operation of the gem as well. If you want to completely remove the dependency on the rebuild function remove super from the code.
Related
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.
How can I call an application controller method inside of a Ruby file? I can't use the traditional MVC architecture.
Thanks!
If the method is meant to be called from both the application controller and some other ruby file, then you need to move that method to another file, probably a Plain Old Ruby Object (PORO). Then, require that file from your controller and from whatever other file needs to use it.
It's a good idea to only have controller-related logic in controllers. Since you're calling this method from something besides a controller, it must not be strictly controller-related logic, so this is the perfect opportunity to move it.
If you have a method in the ApplicationController and you need to call it you can use a trick which is:
ApplicationController.new.#method_here
But you better move the method to a plugin and call it in the ApplicationController for a best practice.
More info here:
How do I call controller/view methods from the console in Rails?
https://stackoverflow.com/a/9159853/2552259
The best approach for your problem is to extract the method in question and put it in a module, like lib/my_utils.rb. Then you can require this file where ever you need it:
# lib/my_utils.rb
module MyUtils
def the_method_you_were_talking_about(args)
.. code ..
end
end
Then:
# application_controller.rb
require 'lib/my_utils.rb'
MyUtils.the_method_you_were_talking_about("and any data you need to pass to it")
And then you'd do the same thing from your other ruby file.
I have foo.gem and there is lib/foo.rb in there.
When I add gem to Gemfile it's foo.rb is automatically required in my path. But I need to include it automatically. Reason for this is I am making console extension and I want them to be available without me writing `include Foo'.
I am experimenting with
SOME_CLASS.send(:include, Foo)
But not sure what class to use to have it added to the path e.g. when I start console that is automatically included. Here are some mixins automatically included in console, I need mine to be there :) Thank you
irb(main):006:0> self.class.included_modules
=> [PP::ObjectMixin, JSON::Ext::Generator::GeneratorMethods::Object, ActiveSupport::Dependencies::Loadable, Kernel]
P.S.
I can solve problem with initializer but I don't want to change project's code I just want to add gem and that it works.
You should use the Kernel module, which is included by Object. It's where private methods like exit, puts and require are defined, so it is an excellent choice for defining an imperative API.
When you extend Object, people expect to be able to call your methods explicitly on any object, and they also understand that your method depends on that object's state.
Kernel methods understood differently. Even though they're technically available to all objects, you don't expect people to write things like:
'some string'.sleep 1000
This makes no sense. sleep has nothing to do with the string; it doesn't depend on it in any way. It should only be called with an implicit receiver, as if the very concept of self didn't exist.
Making your methods private and extending Kernel instead helps you get that message across.
You can do that in foo.rb:
module Foo
# …
end
Some::Class.send :include, Foo
When you load or require some file, it is executed line by line. You can put arbitrary code anywhere in the file, even inside module and class definitions. You can take advantage of that in order to properly set up your library so that others don't have to.
Did you tried
Object.send(:include,Foo)
or
self.send(:include,Foo)
inside your console
In a certain module that I am using in my application, I would like to use the method alarm_path(alarm). How can I include this method in my current module?
I already tried to include ActionView::Helpers::UrlHelper, though, it still complain that this method does not exists.
Simply add this instead of your current include:
include Rails.application.routes.url_helpers
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. :)