Base/Home directory of the gem - ruby-on-rails

As part of the RSpec, i need to reference a file contained in a gem I am depending on (call it gem foo). I control gem foo, so I can make changes to it as needed.
Gem 'foo' contains a file that I need to reference with in the a rspec spec. Is there a reasonably stable RubyGem or Bundler API to figure out 'foo' base directory?
Assuming 'foo' is already required in my Gemfile:
in Gemfile:
gem 'foo'
I want to do something like this (in something_spec.rb):
filename = File.expand_path('examples/xml/result.xml', Gem.gem_base_path('foo'))
What is gem_base_path API call?

I would recommend creating a function in your gem to do this:
module Foo
class Configuration
def self.result_xml_path
File.realpath("../examples/xml/result.xml")
end
end
end
You can then do the following in your spec:
filename = Foo::Configuration.result_xml_path
This is much safer since you are getting all the information from the gem. It also looks cleaner.

This may do what you need without ant need to touch the 'foo' gem:
matches = Gem::Specification.find_all_by_name 'foo'
spec = matches.first
filename = File.expand_path('examples/xml/result.xml', spec.full_gem_path)
I have used this code to make something similar to what you need (namely loading in my specs some factories defined in a gem used by my project)

Related

Rails: How to use initializer code inside gem

I'm using the okcomputer gem for a Rails app (All you need to do to use this gem is to place some code in an initializer).
I'd like to wrap this gem inside a custom gem that can be used without an initializer. I hope to be able to use this gem in several dockerized microservices just by installing it.
I read that it's possible to put initializer code in an init.rb file at the root of the gem. In my case, that doesn't seem to be working (the routes generated by okcomputer are not found), but I'm not sure where the issue lies.
In general, can I expect code in init.rb to behave like code in an initializer?
Your best approach is probably to create a Railties and run your code in an after_initialize hook.
module Gemname
class MyCoolRailtie < ::Rails::Railtie
config.after_initialize do
OKComputer::Registry.register "resque_critical",
OKComputer::ResqueBackedUpCheck.new("critical", 10)
end
end
end
https://api.rubyonrails.org/classes/Rails/Railtie.html

Rails bootstrap gem monkeypatching method not working

I'm using the excellent twitter-bootstrap-rails gem. There is a helper within that gem (NavbarHelper) which is used to generate Bootstrap navbars with a Ruby helper. I want to monkey patch the gem such that the dropdown lists won't have carets.
So, I looked into the source and found the relevant method here. All I have to do is override it. I created a new file in config/initializers called navbar.rb with the following content:
NavbarHelper.module_eval do
def name_and_caret(name)
"HELLO WORLD"
end
end
Presumably, all of the dropdown titles then should be rendered as "HELLO WORLD" in my app (as referenced by the gem source). However, this is not occurring, and the gem does not appear to be monkeypatched at all.
I tried placing puts NavbarHelper.methods - Object.methods in the initializers file, and there were no results, which makes me think that Rails is not loading the gem correctly before the initializers. I have also checked and verified that the gem is not using autoload for its helpers.
Edit
What may be complicating this is the fact that my Gemfile includes the gem in the following manner:
gem 'twitter-bootstrap-rails', git: 'git://github.com/seyhunak/twitter-bootstrap-rails.git', branch: 'bootstrap3'
I'm not sure if this specific versioning means the monkeypatching doesn't work.
Edit #2
It seems there is only one version of the gem on my system, so I don't think that's the issue. Also, I have tried placing require 'twitter-bootstrap-rails at the top of the initializers file, with no results.
The problem is that you patch the method on this module but the module already got included at this point. Try to define this in your application_helper.rb
def name_and_caret(name)
super("blub #{name}")
end

Patching enumerations_mixin gem for rails >=3.2.8

enumerations_mixin gem depends on deprecated method
here's the guilty line
what would be correct approach to patch it?
Quite a lot of patching I am afraid. This method has been replaced by class_attribute, however it works slightly different. Previously it was enough to use write_inheritable_attribute to create new class param, now you need to declare it first and then assign value.
On line 17 it is using 'write_inheritable_attribute` to set those values. It should now read
class_attribute :"acts_enumerated_#{key}" unless respond_to? "acts_enumerated_#{key}"
self.send(:"acts_enumerated_#{key}=", options[key])
Then, everywhere it is using read_inheritable_attribute(:attribute_name) just use self.attribute_name.
The only problem with this is that 'read_inheritable_attribute` returned nil if attribute is not set and the approach above will throw an error. You will notice that all read methods has default value, like (line 56):
read_inheritable_attribute(:acts_enumerated_on_lookup_failure) || :enforce_strict_literals
You will need to look for all those defaults and enforce them within acts as enumerated method:
def acts_as_enumerated(options = {})
valid_keys = [:conditions, :order, :on_lookup_failure]
default_options = {<all the default values from the code>}
options = default_options.merge options
options.assert_valid_keys(*valid_keys)
valid_keys.each do |key|
write_inheritable_attribute("acts_enumerated_#{key.to_s}".to_sym, options[key]) if options.has_key? key
end
However this is not a perfect design. I would probably define class_attribute enumerated_options within append_features method, put all the options there as a hash instead of creating class_attribute for each option. This is absolutely up to you though.
Also please notice that this gem has been written over 4 years ago, and this method might be not the only deprecated one. I am not entirely sure what this gem is supposed to do, but it might be easier to rather implement what you need rather than to use it.
Clone the gem's repo locally and use it in your rails project as a path to a local gem. So specify in your gemfile:
gem :enumerations_mixin, :path => '/local/path/to/gem'
When you will have the gem patched, fork it on github, replace gems origin in /local/path/to/gem/.git/config, push your changes to your fork, and replace that line in your gemfile to the follownig:
gem :enumerations_mixin, :github => 'your_acoount/enumerations_mixin'
Issue the pull-request to the root repo of the gem, and when the request will be accepted, replace that line in your gemfile to the follownig:
gem :enumerations_mixin, :github => 'protocool/enumerations_mixin'
When the gem will have been released the line can be replaced to:
gem :enumerations_mixin, '~> <new_verison>'

Require a gem only in the context of a single class in Ruby

I have written a gem that uses a gem in one of its classes to overwrite Timeout. I only want to overwrite Timeout in this single instance, not globally in my gem and certainly not globally in anyone's project that uses my gem.
The problem I'm running into is when I include my gem in a rails project. It seems that the timeout gem gets instantiated from the get-go (at rails app load) and affects other parts of my Rails app that rely on the standard Timeout.
My question is this: how can I limit the timeout gem's influence to only the single class that I wish to use it in. I've already placed the require statement within the class definition and this didn't seem to help.
Place require: false in your Gemfile. E.g.:
gem 'name-of-gem', require: false
That way the gem won't be required automatically on app load, only when you explicitly call require 'name-of-gem' in your model.
(If that gives you errors you may be using an older version of Ruby, so you'll have to write :require => false instead of require: false)
I think you should fork this gem and add a namespace to Timeout module like this:
require "timeout"
module NewTimeout
module Timeout
#overwrited stuff
end
end
And then use it in your gem directly:
NewTimeout::Timeout.timeout()
Or include in your class:
class SomeClass
include NewTimeout::Timeout
end
And default Timeout module left unchanged
This is not possible without cooperation of the gem author.
When you require a file, the code in the file gets run. Period. If the code in the file monkeypatches a class or modifies the global namespace or whatever, that will happen. Period.
You can use load instead of require which allows you to evaluate the file within the context of an anonymous module. But, of course, using the namespace operator :: the code in the file can still get access to the global namespace and every module and class in it, by just doing something like class ::String; def length; 42 end end.
Now, if the gem author published his gem as a refinement, then you would at least be able to limit the effects to a single script body:
string_with_upper_reverse.rb:
module UpperReverse
def reverse
super.upcase
end
end
module StringWithUpperReverse
refine String do
prepend UpperReverse
end
end
puts 'Hello'.reverse
# olleH
using StringWithUpperReverse
puts 'Hello'.reverse
# OLLEH
test.rb:
puts 'Hello'.reverse
# olleH
require_relative 'string_with_upper_reverse'
puts 'Hello'.reverse
# olleH
using StringWithUpperReverse
puts 'Hello'.reverse
# OLLEH
require_relative 'required'
required.rb:
puts 'Hello'.reverse
# olleH
using StringWithUpperReverse
puts 'Hello'.reverse
# OLLEH
As you can see, neither requireing nor being required by a script which is using the refinement will mean that the refinement is visible inside your script. Only explicitly using the refinement inside a script body will make the refinement visible only from that point on and only within that one script body.
Note, however, that refinements are an experimental feature: their API may change without notice in future versions of Ruby, they may even be removed altogether.
Note also that the version of refinements that ships with Ruby 2.0.0 (and also current development versions of Ruby 2.1) can only be scoped to script bodies. The ability to be scoped to module and class bodies was removed shortly prior to the release of Ruby 2.0.0, together with the Module#using method.
And lastly, note that Object#send ignores refinements:
require_relative 'string_with_upper_reverse'
using StringWithUpperReverse
puts 'Hello'.reverse
# OLLEH
puts 'Hello'.send(:reverse)
# olleH

how can we include installed gem in controller in RoR?

I have installed gem successfully.But how can we use use in controller?
Look inside of config/environment.rb. Inside of the Rails::Initializer.run block you should see a commented-out note that describes using config.gem.
You want to add the gem that you need with that method, like this:
config.gem "foo"
There are other options that you might need, depending on what gem you are trying to use. Mention what it is, and I can be more specific.
Also be sure to read the docs for the gem method.
Add require 'gem' to top of controller or specific method.
Sample use of rubyzip gem for ex
def zip(data, filename)
require 'zip/zip'
require 'zip/zipfilesystem'
zipfile = "/tmp/rubyzip-#{rand 32768}"
Zip::ZipOutputStream::open(zipfile) do |io|
io.put_next_entry(filename)
io.write data
end
zippy = File.open(zipfile).read
File.delete(zipfile)
zippy
end

Resources