Combining yard docs for multiple gems - ruby

I'm using YARD to generate docs for a couple projects that I'm working on. In one, I'm co-developing a gem that hosts a lot of redundant and shared resources that will be used by other projects.
What'd I'd like is for references from the first project to classes or methods in the gem to show up in the docs for the main project. For example
Gem
class Widget
# Spins the widget of course.
def spin
end
end
Project
class SpecialWidget
# Jump and {#spin}.
def dance
end
end
I'd like the docs for SpecialWidget to generate an actual link to the Widget#spin method.
Can this be done?

Related

undefined local variable or method for extended module

NOTE: the question was edited to reflect the issue.
i would like to work with knife-api rubygem. enclosed a snippet of my code
require 'chef'
require 'chef/knife'
require 'knife/api'
module X
module Y
module Z
include Chef::Knife::API
def self.foo
resp = knife_capture :search, ['*:*']
puts resp
end
end
end
end
when X::Y::Z.foo is called, it returns
gems/knife-api-0.1.7/lib/knife/api.rb:41:in `ensure in knife_capture': undefined local variable or method `revert_io_channels' for X::Y::Z (NameError)
it appears that he enclosing scope functions (Chef::Knife within knife-api) are inaccessible within X::Y::Z.foo.
how should i make this work?
In a complete non-answer, do not use the knife-api gem. It should not exist. The correct gem to use if you want to access Chef API data from Ruby code is chef-api (I know, we're real creative with names). If you want to do something very small, check out the knife exec command which just exposes the raw Chef object API (not as refined as chef-api, but built in).
This gem is a fork of another project that is no longer maintained. Looking at the gem code, it appears there were several issues introduced to the latest version (0.1.7) through some bad refactoring of the original code. That version was released a year ago and it hasn't had any commits since. The repo also does not accept issue tickets, and it has no tests. If you must use this gem, I would try a pessimistic constraint gem 'knife-api', '< 0.1.7' and see if that works. However it might be a better idea to skip it entirely. I made an attempt to fix the issues and submit a PR. You can also try pulling the gem from my forked repo https://github.com/msimonborg/knife-api

Rails mount engine while allowing to subclass any engine class

I'm building a platform in Rails that will act as a basis for a few other Rails applications. Ideally I would like this to be a shared engine, on which the other concrete applications can build.
It would be nice if the applications could extend the base classes that the engine provides. AFAIK this can be done using monkey patching, but that feels 'hacky' to me.
I stumbled onto what looked like the solution, which was to just create 'mirror' classes in the main Rails app, which extend those of the engine:
# SharedEngine/models/shared_engine/post.rb
module SharedEngine
class Post < ActiveRecord::Base
def hello
"Hello"
end
end
end
# App/models/shared_engine/post.rb
require SharedEngine::Engine.root.join('app', 'models', 'shared_engine', 'post')
class Post < SharedEngine::Post
def hello
super + " world"
end
end
However, it looks like there are some autoloading problems. After server startup, it prints "Hello". Then after I save the app model, it says "Hello world".
The Rails engine guide suggests putting shared code into concerns. Is there any other way to get this working cleanly without having to create concerns for every class?
Since you need to create a basis for a few Rails apps to reduce boilerplate i suggest having a look a Rails Application Templates. Using templates you may provide all the necessary scaffolding without monkey-patching any class.

How can I write code that needs to run after some gem was loaded, but without requiring that gem?

I am writing a gem that "hooks" onto other gems. It's designed to work with multiple different gems that do similar things, this adds functionality onto any of them that may be present.
What my gem does at startup is something like this:
if defined?(GemAModule)
# Hook onto Gem A's observer methods
end
if defined?(GemBModule)
# Hook onto Gem B's observer methods
end
Because of this, I need this code to run after A and / or B have been loaded. But I can't require either A or B. I want this to work for people that use only A, or only B, so A and B are not in my gem's dependencies, and they can't be in require statements.
I could do this by having a: MyGem.hook() method that users call in their initializers, after they have initialized A or B or both. But I'm trying to avoid that. I want to have it so that adding my gem just "magically" work.
Is this possible? What is the best way to do this?
Can I somehow check if "a gem is in the gemfile", and in that case require the module?
You could make this work in the case where your gem is loaded first by overriding require. Combined with your code that works in the case when your gem is loaded after the other gems, this should solve the problem. Something like this:
module Kernel
alias original_require require
def require(name)
if original_require(name)
case name
when 'gem_A'
# stuff goes here
when 'gem_B'
# more stuff goes here
end
end
end
end
What I personally would do in this situation is have your users require your gem in different ways depending on which other gem they are using, for example:
# if using gem A
require 'gem_A'
require 'your_gem/gem_A_compat'
# if using gem B
require 'gem_B'
require 'your_gem/gem_B_compat'
Don't auto-detect code setup, or do anything overly complicated or "clever", you will regret it when it comes to testing and debugging (you will need both compatible gems in your development dependencies and have tests that load each in turn). Instead, document your own gem's requires so that the end user loads your gem depending on the link they wish to make.
So, assuming your gem is called my_gem, you would have files:
lib/my_gem/gem_a.rb
require 'my_gem'
require 'gem_a'
# Require Hook onto Gem A's observer methods
lib/my_gem/gem_b.rb
require 'my_gem'
require 'gem_b'
# Require Hook onto Gem B's observer methods
And you instruct the end user to require your gem as follows:
require 'my_gem/gem_a'
or . . .
require 'my_gem/gem_b'
as needed.
This is no more work for your gem user, and a lot easier to test, develop and debug for you and developers that use your gem.

Require Dependency Only On Certain Method Call

I have a library of functions that is packaged up as a gem. One of these functions requires the use of a third-party gem that itself has a long list of dependencies.
Is there a way that I can set up my gem so that users can install it and use those functions in the gem that don't have the third-party dependency without the runtime complaining?
I want an exception to be raised when the method with the dependency is called, but the user should be able to use the other functions without a runtime error.
Is this possible?
You can split your functions to modules (maybe modules to files too) that are depending or not depending gem. including GemDependent module trying to require your gem if not then redefine all gem dependent functions to raise exception.
module YourFunctions
module GemDependent
def self.included(klass)
require "yourgem"
rescue LoadError
instance_methods.each do |m|
define_method(m) { raise "you need yourgem to run #{m}" }
end
end
def gem_dependent_function
end
end
include GemDependent
def no_dependent_function
end
end
include YourFunctions
gem_dependent_function
# "you need yourgem to run gem_dependent_function"
if you want to know how to do this, have a look at the various wrapper libraries for http, json, yaml etc.
a good example is https://github.com/rubiii/httpi where you can configure one of 3 http adapters. the adapters themselves require the individual dependencies within ruby.
the problem is, that the end-user needs to somehow know how to install the 3rd party gems. it's currently not possible to implement optional dependencies within ruby gemspecs.

How can I build a modular command-line interface using rubygems?

I've written a command-line tool for manipulating with genome scaffolds called "Scaffolder". At the moment all the tools I want to use are hard-coded into the library. For instance these tools "validate" or "build" the scaffold. I'd like to split these tools out into their own gems, make it more modular, and to allow third parties to write their own commands.
The ideal case would be that I run "gem install scaffolder-validate" and this gem-bundled command would then be available as part of scaffolder. I know a couple of libraries make it easy to build a command-line interface: thor, commander, gli, .... However I don't think any of them cater for this type of functionality.
My question is how can I use a gem structure to create a module structure for installing these commands? Specifically how can the installed commands be auto-detected and loaded? With some prefix in the gem name scaffolder-* then searching rubygems? How could I test this with cucumber?
So, one thing you can do is to decide on a canonical name for your plugins, and then use that convention to load things dynamically.
It looks like your code is all under a module Scaffolder, so you can create plugins following the following rules:
Scaffolder gems must be named scaffold-tools-plugin-pluginname
All plugins expose one class, named Scaffolder::Plugin::Pluginname
That class must conform to some interface you document, and possibly provide a base class for
Given that, you can then accept a command-line argument of the plugins to load (assuming OptionParser):
plugin_names = []
opts.on('--plugins PLUGINS','List of plugins') do |plug|
plugin_names << plug
end
Then:
plugin_classes = []
plugin_names.each do |plugin_name|
require "scaffold-tools-plugin-#{plugin_name}"
plugin_classes << Kernel.const_get("Scaffold::Plugin::#{plugin_name}")
end
Now plugin_classes is an Array of the class objects for the plugins configured. Supposing they all conform to some common constructor and some common methods:
plugin_classes.each do |plugin_class|
plugin = plugin_class.new(args)
plugin.do_its_thing(other,args)
end
Obviously, when doing a lot of dynamic class loading like this, you need to be careful and trust the code that you are running. I'm assuming for such a small domain, it won't be a concern, but just be wary of requireing random code.
Hm, tricky one. One simple idea I have is that the main gem just tries to require all the others and catches the load error when they are not there and disables the respective features. I do this in one of my gems. If HighLine is present, the user gets prompted for a password, if it isn't there has to be a config file.
begin
require 'highline'
rescue LoadError
highline = false
end
If you have a lot of gems this could become ugly though...

Resources