I want to add some functionality to the ActiveMerchant gem in order to test the PayPal Express gateway, a pull request has been attempted for this but was turned down on Github.
I want to add a single class to the ActiveMerchant Billing module:
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class PaypalBogusGateway < BogusGateway
# some codes here
end
end
end
I have done this successfully by downloading and pulling the gem into my project locally and trhowing my new file in there:
#Gemfile
gem 'activemerchant', '1.34.1', path: "vendor/gems/activemerchant-1.34.1", require: 'active_merchant'
But of course, that's not the best idea because I'll have to manually pull any updates if I want them.
Is there any way I can add the class to their module using their gem that's been pulled from the RubyGems source?
Thanks
EDIT
Putting it in the lib folder should work but my code requires some classes from the gem to inherit from, like:
require File.dirname(__FILE__) + '/paypal/paypal_common_api'
require File.dirname(__FILE__) + '/paypal/paypal_express_response'
require File.dirname(__FILE__) + '/paypal_express_common'
replacing File.dirname(FILE) with wherever the gem is installed... This will be different across server environments right?
Add activemerchant to the Gemfile, bundle install
In config/application.rb make sure lib is included in the autoload paths
# need to uncomment or add this to the configuration
config.autoload_paths += %W(#{config.root}/lib)
place your class in a file using nested directories to match the modules
# lib/active_merchant/billing/paypal_bogus_gateway.rb
do NOT include any require statements in your bogus gateway, rails (via bundler should require everything from the Gemfile)
restart rails
You might want to just fork the project on GitHub and add your changes to it. Even if it is just a single class. And then, in your Gemfile, do this:
gem "active_merchant", :git => "git://github.com/<your-user-name-here>/active_merchant.git"
Related
We are writing a gem that includes multiple common gems used in a couple of our apps. We want to be able to have some kind of config to require or not require a certain gems. But the application.rb or enviroment.rb/*rb is loaded after gems is loaded. So we can not set a config there. The following way works but its not a good idea and I was wondering if there is a cleaner way to do this
bin/rails . (In consuming app)
#!/usr/bin/env ruby
DISABLE_TRANSPORT = true
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
Gemfile (In consuming app)
gem 'fruit_chain'
fruit_chain/lib/fruit_chain.rb (Our gem)
require analytic
require transport unless defined?(DISABLE_TRANSPORT) && DISABLE_TRANSPORT
require marketing
...
module FruitChain
end
I'm not sure to understand what you want exactly... however, use require: false in the Gemfile may help you ?
# Gemfile
gem 'my_super_gem', require: false
And in an initializer you require them:
# config/initializer/fruit_chain.rb
require 'my_super_gem'
I've encountered the same problem and have only thought to include the gems explicitly as a responsibility of the app, and then injecting that gem into the parent gem through a configuration, and ensuring they conform to the same interface.
I am developing a Rails app where most of the code not specific to the app has been written inside of various gems, including some Rails engines and some 3rd party gems for which I am enhancing or fixing bugs.
gem 'mygem', path: File.expath_path('../../mygem', __FILE__)
Since a lot of the code in these gems is really part of the app, it's still changing frequently. I'd like to be able to utilize the Rails feature where code is reloaded on each request when in development (i.e. when config.cache_classes is false), but this is only done within the normal application structure by default.
How can I configure Rails to reload gem code on each request, just like with the app code?
I have found through trial and error that several steps are required, with the help of ActiveSupport.
Add activesupport as a dependency in the .gemspec files
spec.add_dependency 'activesupport'
Include ActiveSupport::Dependencies in the top-level module of your gem (this was the most elusive requirement)
require 'bundler'; Bundler.setup
require 'active_support/dependencies'
module MyGem
unloadable
include ActiveSupport::Dependencies
end
require 'my_gem/version.rb'
# etc...
Set up your gem to use autoloading. You an either manually use ruby autoload declarations to map symbols into filenames, or use the Rails-style folder-structure-to-module-hierarchy rules (see ActiveSupport #constantize)
In each module and class in your gem, add unloadable.
module MyModule
unloadable
end
In each file that depends on a module or class from the gem, including in the gem itself, declare them at the top of each file using require_dependency. Look up the path of the gem as necessary to properly resolve the paths.
require_dependency "#{Gem.loaded_specs['my_gem'].full_gem_path}/lib/my_gem/myclass"
If you get exceptions after modifying a file and making a request, check that you haven't missed a dependency.
For some interesting details see this comprehensive post on Rails (and ruby) autoloading.
The solution that I used for Rails 6, with a dedicated Zeitwerk class loader and file checker :
Add the gem to the Rails project using the path: option in Gemfile
gem 'mygem', path: 'TODO' # The root directory of the local gem
In the development.rb, setup the classloader and the file watcher
gem_path = 'TODO' # The root directory of the local gem, the same used in Gemfile
# Create a Zeitwerk class loader for each gem
gem_lib_path = gem_path.join('lib').join(gem_path.basename)
gem_loader = Zeitwerk::Registry.loader_for_gem(gem_lib_path)
gem_loader.enable_reloading
gem_loader.setup
# Create a file watcher that will reload the gem classes when a file changes
file_watcher = ActiveSupport::FileUpdateChecker.new(gem_path.glob('**/*')) do
gem_loader.reload
end
# Plug it to Rails to be executed on each request
Rails.application.reloaders << Class.new do
def initialize(file_watcher)
#file_watcher = file_watcher
end
def updated?
#file_watcher.execute_if_updated
end
end.new(file_watcher)
With this, on each request, the class loader will reload the gem classes if one of them has been modified.
For a detailed walkthrough, see my article Embed a gem in a Rails project and enable autoreload.
I'm not sure if this sort of thing is very common, but I keep finding myself trying to create gems that are just wrappers around a Rails application.
My gem will have a generator to create a config.ru but the Rails application will live inside the gem's lib directory. I need to know how to "embed" a Rails application and configure it so that it can be run inside the gem.
For example:
$ mygem new project
mygem created a directory called "project" with the following files:
project/config.ru
project/widgets/
project/foobars/
My gem will also generate some directories that will need to be added to Rails somehow so that I can access the code in those directories from the Rails app living inside the Gem.
Any help or advice you can give me would be appreciated.
To clarify, I'm not trying to create a Rails engine, or plugin to a Rails application. I'm trying to create a fully-fledged Rails application, but package it as a gem so that a user of my gem can run the gem (the rails app) without needing to know that it's using Rails behind the scenes.
Update: Okay, I've got a little bit working now. I've created the gem and generated the rails project inside the gem's lib directory.
$ bundle gem my_gem && cd my_gem/lib
$ rails new my_gem --skip-bundle
Which leaves me with:
my_gem/
my_gem.gemspec
bin/my_gem
lib/
my_gem.rb
my_gem/
version.rb # generated by bundler
# the rails app:
app/
config/
Gemfile
...etc
Since this gem requires Rails, I started adding the gems defined in the Rails Gemfile as dependencies in the gem's Gemspec, but I'm a little confused as to how to handle the assets group in the Gemfile.
# Rails Gemfile
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
gem 'therubyracer', :platforms => :ruby
gem 'uglifier', '>= 1.0.3'
end
# gemspec
Gem::Specification.new do |gem|
gem.name = "my_gem"
# ...
gem.add_dependency 'rails', '3.2.8'
gem.add_dependency 'sqlite3'
gem.add_dependency 'jquery-rails'
# how to add the assets group gems?
end
Try this and see if it helps you make progress.
Gems are just directories of files, and you can put whatever files you want into a gem.
Create:
Create a blank gem full-blown Rails project:
$ bundle gem my_gem
Then a Rails app:
$ rails new my_app --skip-bundle
Copy the Rails files into the gem:
$ cp -R my_app/* my_gem
Bundle everything into your Rails app:
$ cd my_gem
$ bundle install --binstubs --path vendor/bundle
$ cd -
Make the Rakefile have the gem tasks and the Rails setup:
#!/usr/bin/env rake
require "bundler/gem_tasks"
require File.expand_path('../config/application', __FILE__)
MyApp::Application.load_tasks
Verify that it starts:
$ rails server
Load Path:
To control where Rails looks for files, such as "external" configuration files, you can use the file config/application.rb with any directory paths like this:
# Add additional load paths for your own custom dirs
# config.load_paths += %W( #{config.root}/../customdir )
Note the ".." which means go above the Rails directory. This gives you a path relative to the gem.
If you prefer you can specify an absolute path, for example if you know the user will always keep his external files in "~/myfiles/". You can also choose to use ENV vars to send in whatever directory you want.
If you read about load path capabilties, look for lines that are shorthand for adding a directory to the front of the load path because you may want to put your external diretories first:
$:.unshift File.dirname(__FILE__)
Gem Build:
Edit my_gem.gemspec to add your own description, homepage, summary, etc. then build:
$ gem build my_gem.gemspec
Successfully built RubyGem
Name: my_gem
Version: 0.0.1
File: my_gem-0.0.1.gem
Now your Rails app is packaged as a gem.
The config.ru should be a typical Rails one. No special changes AFAIK.
When your user wants to install your app:
$ gem install my_gem
The gem will install in the user's typical gem directory. If you want to adjust this, see this page on rubygems: http://docs.rubygems.org/read/chapter/3
Crate:
You may also want to investigate the Crate project:
Crate: Packaging Standalone Ruby Applications
http://www.slideshare.net/copiousfreetime/crate-packaging-standalone-ruby-applications
Rack:
To use config.ru here is the typical Rails setup:
# Rails.root/config.ru
require "config/environment"
use Rails::Rack::LogTailer
use ActionDispatch::Static
run ActionController::Dispatcher.new
For your project, you want to require some files before Rails. You'll want to learn about the Ruby "require" and how it finds files using LOAD_PATH.
The easy way:
# Rails.root/config.ru
require_relative 'filename'
require "config/environment"
Or to put the user's custom directory up couple directory levels:
require './../../filename' # not the best for security
Or to use an absolute path, read about File.expand_path:
File.expand_path(__FILE__)
Or to use the current directory and put it on the load path:
$LOAD_PATH.unshift(File.dirname(__FILE__))
require 'filename'
Lots of choices for you to consider. Hope this helps!
What about the question, "How am I going to run the Rails application inside the gem?".
A Rails application has controllers and views to run a web server. What you need are actions to create, list, update, and destroy. Exposing these actions without a web server is essentially having such methods in a class. That's a normal standard type of gem in the first place.
So maybe your questions is really, how do I write a gem where I have ActiveRecord, and the other Rails stuff.
First, you need to make your gem dependent on the Rails gems you need. You do this in the gemspec file for your gem.
Then it really is just a matter of your gem code doing a require of the right Rails gems you need.
I'm not sure if this will help, as I read through everything and I couldn't find the motivation behind why you were doing this. One of the reasons I came up with was making something that can be used on a desktop environment. In that case you could try using something like Bowline. If you just want to provide an application that others can download and use and install themselves, then you can probably assume they can follow at least basic developer kind of instructions and you could just provide the whole app on github or as a zip file. See an example of someone else doing something similar over on Fat Free CRM's github page.
I'm building a gem locally, suppose it name is "MyGem".
Now suppose that MyGem depends on other already built gem, for example "cancan".
So, i've added in my mygem.gemspec the line:
add_runtime_dependency("cancan")
Here is the problem: if I installl MyGem into a new project by adding it into my gemfile like this:
gem "mygem", :path => "path/to/my/local/gem"
then this new project is not being able to use cancan methods, and I have to explicity declare cancan on the new project gemfile in order to use it.
I tried also using gemspec method, but didn't solve my problem either.
Any ideas?
Update
I just wanted to add that when i only have myGem declared in my new application gemfile, after I run bundle install all the dependencies are installed.
That is, if i run gem list the "cancan" gem is displayed, but I still can't access it methods from the application level.
Thanks for the help.
Ok, i've solved this. I'm not sure if this is the best solution but it did work.
Making the application level developer to explicity add the dependencies in his gemfile didn't make any sense. So, as the gems did were being installed, i just required all the gem dependencies in my my_engine.rb file inside my gem.
Following the example, in my my_engine.rb I added the following line
require "cancan"
And that'it...
Even better you could do:
autoload :CanCan, "cancan"
So the module would be loaded only when it is called.
And even better than that, you could load only the file from cancan that you are using (maybe you don't need to load all of it).
You can add that line in you my_gem.rb file or your engine.rb file if you are using engines.
That worked for me, I hope this help someone.
Use add_dependency instead of add_runtime_dependency (this may help with cancan) then run bundle update on your new project.
Does the "cancan" gem name actually match the name to be require'd?
Example for yajl-ruby gem:
Gem::Specification.new do |s|
s.name = %q{yajl-ruby}
...
But when require'ing the library, you'd use a different string:
require 'yajl'
That means in your Gemfile, you have to explicitly require the dependency (which you said you wanted to avoid).
gem 'yajl-ruby', '>=1.0', :require => 'yajl'
To avoid needing to do this, and if you're the author of the "cancan" dependency, you should make the gem name matches the require name.
The only alternative I can think of is require'ing the dependency directly in one of your source files (like you did in your solution).
What do you do when you want to use a gem for development/testing that you don't want to force other devs to use? Right now I have
begin
require 'redgreen'
rescue LoadError
end
in test_helper.rb and no gem config, but that seems like a clumsy approach, albeit a functional one. I'd like to do something like the following:
config.gem "redgreen", :optional => true
Any other suggestions? Or should I just vendor those pretty superficial gems...?
EDIT
To be clear, I am only talking about those specific gems, like redgreen, which aren't actually used in the functional code, but only in the coding process. There is no need to vendor these at all, except to avoid the conditional require.
Gems that are specific to your development environment should be installed in your gemset or local gems, but not in the Gemfile.
A classic example is the ruby-debug-base19x which Rubymine needs for debugging. This is installed in your local gemset, but not in the Gemfile because not all coders use Rubymine.
[EDIT]
Indeed, everything is run in the context of the bundle, and outside gems are not reachable. There do exist some workarounds indeed. Most of them are dirty :)
I found a lot of good solutions in this bundler issue.
The nicest solution was to add this to your .irbrc :
# Add all gems in the global gemset to the $LOAD_PATH so they can be used even
# in places like 'rails console'.
if defined?(::Bundler)
global_gemset = ENV['GEM_PATH'].split(':').grep(/ruby.*#global/).first
if global_gemset
all_global_gem_paths = Dir.glob("#{global_gemset}/gems/*")
all_global_gem_paths.each do |p|
gem_path = "#{p}/lib"
$LOAD_PATH << gem_path
end
end
end
require 'irb/completion'
require 'rubygems'
require 'wirble'
Wirble.init
Wirble.colorize
If you then install wirble to the global gemset, it can then be found.
Original source: https://gist.github.com/794915
Hope this helps.
I answered a similar question of my own here
User-level bundler Gemfile
One way to do this is to create different environments:
group :scott do
end
Then
bundle --with-env=scott
Ok, I think I've come up with something. Basically, the idea is to only execute a secondary Gemfile when a Rails app is executing. To do this we add two things:
First, we alter the rails script a little:
# in ./script/rails
Kernel::IN_RAILS_APP = true
APP_PATH = File.expand_path('../../config/application', __FILE__)
require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands'
Second, we tell bundler to pull in the secondary Gemfile if we're in a rails app and a secondary file exists:
# Gemfile
if Kernel.const_defined?(:IN_RAILS_APP)
local_gemfile = File.dirname(__FILE__) + "/Gemfile.local"
if File.exists?(local_gemfile)
puts 'using local gemfile'
self.instance_eval(Bundler.read_file(local_gemfile))
end
end
Now you can add a Gemfile.local to your project and run specific gems on a per-machine basis. bundle install works normally since the IN_RAILS_APP constant doesn't exist.
** Make sure to add Gemfile.local to your .gitignore.
In my opinions this is what environments are for. Fortunately there is also a way provided to do it with what is in your Gemfile, this is also how rails use it: groups
Pretty much use the environments the same way rails use it. Here is what you could find in your Gemfile:
group :test do
# Pretty printed test output
gem 'turn', :require => false
end
And here is what you can find in your config/application.rb
Bundler.require(:default, Rails.env) if defined?(Bundler)
All you would need to do is to change your local environment settings and the others working with you won't be affected unless they decide to. Everything gets committed and nothing gets lost.
Here some links :
http://yehudakatz.com/2010/05/09/the-how-and-why-of-bundler-groups/
http://gembundler.com/groups.html
If you want it to be optional, it's better to freeze the gem as a plugin. However, it's not a good idea to use different gems than the rest of a development team, as it creates some inconsistencies in the codebase that can be hard to track down later. I would say add it to config.gem, and just tell the other developers to do:
rake gems:install
And you're done.
This is how I tackled the same problem under Rails 3.1. In my Gemfile:
if File.exists? './tmp/eric_dev_gems'
gem 'redgreen'
gem 'awesome_print'
gem 'wirble'
gem 'wirb'
gem 'hirb'
end
Create a file in ./tmp/ (or in some folder which is in your .gitignore) of your choosing. I used eric_dev_gems. This should be ignored by git, and will only exist on your system unless one of your teammates decides he wants to create that file too.
I solved it by putting this in my gem file:
$gem_names ||= ENV['GEM_PATH'].split(':').map{|g| Dir.glob("#{g}/gems/*").map{|p|p.split('/gems/').last}}.flatten
gem 'redgreen' if $gem_names.any?{|n| n=~/redgreen/ }
That way the gem will only be used if you manually installed it on your system.
This works well but has the downside that it puts the gem name in the Gemfile.lock. This is of little consequence because the gem does not get installed with bundle install but it does make your lock file a bit messy and can cause the lock file to change a bit from one developer to the next.
If that is an issue for you another option is to keep the gemfile clean and require the gem by its full path, or you can add the path for just that gem. Like this:
$gem_paths ||= ENV['GEM_PATH'].split(':').map{|g| Dir.glob("#{g}/gems/*")}.flatten
$gem_paths.grep(/redgreen/).each {|p|$LOAD_PATH << p+'/lib'}
require 'redgreen' rescue nil