Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending a ruby gem in Rails

Let's say I have a Rails app that gets most of it's functionality from a gem (for instance, a CMS).

If I now need to add some customisation (for instance, add a property to a user) what is the best practice way of doing this? If I customise the gem, then I will have issues with updating the gem in the future.

What is the best approach to take here?

like image 865
Neil Middleton Avatar asked Jan 06 '10 11:01

Neil Middleton


People also ask

What is extended Ruby?

Extend is also used to importing module code but extends import them as class methods. Ruby will throw an error when we try to access methods of import module with the instance of the class because the module gets import to the superclass just as the instance of the extended module.

How do Ruby extensions deal with gems?

If you are going to support multiple languages, such as C and Java extensions, you should put the C-specific ruby files under the ext/ directory as well in a lib/ directory. When the extension is built the files in ext/my_malloc/lib/ will be installed into the lib/ directory for you.

How do I push a gem to RubyGems?

Note that you need an account at RubyGems.org to do this. From the main menu, select Tools | Gem | Push Gem. In the Run tool window, specify your RubyGems credentials. Your gem will be published to RubyGems.org.

What are native extensions Ruby?

Ruby native extensions are libraries written in C using the built-in functions of the RubyVM. Basically, it's C programming with a ton of functions and macros to interact with the virtual machine. Anything that can be achieved by using pure Ruby can also be implemented using the built-in instructions.


2 Answers

This question is quite old, but I feel it could use a bit more fleshing out. It is true that you can monkeypatch rails (and ruby) at run-time. That means it's easy to reopen a class or module and inject new code. However, this is somewhat trickier in rails due to all the dynamic class loading and unloading that goes on development mode.

I won't go into details, but you really want to put your extensions into an initializer, or a gem, since they get reloaded between requests in dev mode. If you put the code into a plugin it won't get reloaded and you'll get very mysterious errors such as "A copy of XXX has been removed from the module tree but is still active!"

The easiest thing to do is throw the code into an initializer (e.g. config/initializers/user_extensions.rb). You can just use class_eval to inject the code.

User.class_eval do
  ... new code ...
end

One major drawback of ruby's extensibility is tracking down where code is coming from. You might want to add some kind of log message about the extensions being loaded, so people can track it down.

Rails.logger.info "\n~~~ Loading extensions to the User model from #{ __FILE__ }\n"
User.class_eval do
  ... new code ...
end

Further reading:

http://airbladesoftware.com/notes/monkey-patching-a-gem-in-rails-2-3

like image 196
James H Avatar answered Sep 20 '22 08:09

James H


Ruby allows you to extend classes in runtime, so you can often hack in to a library without touching the source code. Otherwise I would suggest that you download the gem, create some hooks in the library and submit that back as a patch.

Update:

Note that these customisations are application specific

Yes. What I meant was to modify the generic api in a way, such that it is possible to customise per application. For example, by allowing the user to pass a block to certain methods etc.

like image 1
troelskn Avatar answered Sep 20 '22 08:09

troelskn