Gotchas for writing rubygems - ruby

There have been questions with answers on how to write rubygems, but what should you avoid in writing a rubygem? What can cause problems for people using your rubygem?

Gem Packaging: Best Practices gives a lot of advice, some of which include
Don't pollute the global load path. Ideally, only have foo.rb in your lib directory, and put all your other files in lib/foo.
Don't require files using __FILE__.
Don't rely on anything outside the load path. Folders may not have the same structure as in your original version. For example, don't use something like
VERSION = ::File.read(::File.join(::File.dirname(FILE), "..", "..", "VERSION")).strip
Don't manage $LOAD_PATH within lib.
Provide a VERSION constant.
Don't depend on rubygems. The person using your code may not be using rubygems, but some other packaging system (or no packaging system). Similarly, don't mention version dependencies in the code itself, or rescue Gem::LoadError.
Rubygems dependencies. Please... argues that you shouldn't list optional runtime dependencies, and should separate developer from runtime dependencies.
From my own experience: if nothing else, try building and installing your gem locally before releasing it into the wild. It avoids brown paper bag releases.

Related

Why the Ruby Gems code have certain file that just requires file with same name but the only difference is they have snakecase?

For our current question, let take this repo as an example.
https://github.com/slack-ruby/slack-ruby-client/tree/master/lib
In this repo, we can see the following list of files.
The slack_ruby_client.rb just has require 'slack-ruby-client' in the file. I am seeing similar convention in few other gems as well. Another example is https://github.com/orbit-love/notion-ruby-client/tree/main/lib
Is there something with the logic in here? Can someone please explain me or direct me to any article that talks about this?
Because a lot of programmers don't understand that slack_ruby_client and slack-ruby-client are not the same thing, and so the authors of the gem provide both files to avoid being flooded with bug reports.
The authors of Nokogiri did the same thing because programmers kept mis-spelling it as require 'nokogirl' instead of require 'nokogiri' and reporting bugs that it is not working, so the Nokogiri authors just put in a file named nokogirl.rb which just contains require 'nokogiri'.
At first, the default entry point of a gem is commonly the exact same file as the name of the gem, as this follows the hardcoded defaults of bundler. There is also a potential conflict of conventions used in naming gems, ruby files and the mapping to constants (classes, modules, ...) in those files.
At first, when adding a gem as a dependency to your Gemfile, bundler may try to automatically require the gem. Here, it uses the name of the gem dependency by default (this can be overridden with the :require option).
Because of this, the normal entry point for the slack-ruby-client and notion-ruby-client gems is named the same as the gem. Bundler is thus able to require the gem code without any further instructions.
Now, there are other ways to require Ruby code than to just rely on bundler autorequiring. Often, developers manually require code to ensure that their dependencies are guaranteed to be loaded, regardless of whether bundler is used or not.
The usual convention when naming ruby code files is to use snake_case names named after the respective class / module defined in the file. A class names MySpecialClass would be defined in a file named my_special_class.rb
Given that the gem is called slack-ruby-client and people being aware of this specific name, they might thus assume that they can or should require the gem as require "slack_ruby_client", assuming that snake_case file names are used here (as would be true most of the case in Ruby). The slack-ruby-client gem supports this assumption by providing an appropriately named file which loads the actual module.
Additionally, when manually requiring code, many people assume that they can just require the desired module using its module name converted to snake_case. In the case of the slack-ruby-client which provides a Slack module, people may thus assume that they can just use require "slack" to load the module.
Accordingly, the gem also provides a file called slack.rb which requires the actual entry point for the gem.
In the end, those files are added by gem authors to provide convenience entry points for people assuming various conventions for naming gems and / or files in them in order to provide the correct functionality seamlessly.

Create a completely self-contained gem?

I am writing my first real gem. It depends on three gems, and those gems have roughly a dozen dependencies. I'd like this all to be self-contained so my gem will always use the dependencies from inside the gem, and will not require the other gems to be installed at the system level. Doable?
I played around with bundle install --path vendor --standalone. It did indeed store all the gems' source code into vendor, but I could then not get my 'requires' to work correctly and the code could not be found.
I suspect either that it's not possible to do this with a gem (only and app) or I was missing some piece of configuration to point my gem's code at the vendored dependencies. I played around with $LOADPATH but could not make it work.
Anyone know if this can be done and, if so, how to make it work? Pointers to info welcome.
I think you need to add those gems as a dependency to your .gemspec file. That way when your gem gets installed, its dependencies will also be installed.
More information can be found here

What's the benefit of version.rb in Ruby gem

It is common to have a version.rb file, defining the version of the gem, like:
module Foo
VERSION = "0.0.1"
end
and in the .gemspec file, require the version.rb and use Foo::VERSION there.
What is the benefit of this convention?
--
Also, sometime when namespace is used, the generated verison file can be deeply in nested folders. Wouldn't it be easier to place a version.rb directly under lib/ and specifying the version there?
This is done this way to reduce churn (change rate) on .gemspec file. I imagine that dependency changes are much more important than version bumps (which are also more frequent). So version changes would add too much noise and hide important commits.
It depends on what gem builder you used. Having a separate file makes it easy to stomp and rebuild as you bump version numbers with rake tasks, for example. this is a lot easier than fiddling with a source file and trying not to damage anything.
Some packagers use a separate VERSION file.
In short, if your gem doesn't need to know the current version itself, then you don't need a version.rb. Even some of the well-known gems like rack don't use it.
Let's say you are building an executable gem that contains an option -v to see the current version, or you have to print current version to warn developers there are some deprecated changes, then to require a separated version file is much more convenient and efficient than parse the gemspec.
It's just a convention in Ruby community. The main reason of why version.rb is everywhere is that lots of gems are built by bundler gem.

Ruby beginner - using /modifying existing gems in single project

As the title states, I'm a beginner in Ruby.
My project uses 2 existing gems - which I want to modify.
I've forked the gems on GitHub and put them as modules in my repo and they show up as a subdirectory.
I've tried researching this, but I keep on getting lost - I'm thinking I'm missing some basic concepts/knowledge here.
My questions:
Am I going about this right/wrong?
Is it even possible to include the code of those (forked) gems in my actual project, or should I update them separately and just use them as actual gems with require (this seems very cumbersome)
If these gems are part of my project, how do I use them properly, I assume I don't need the require piece? If not, how do I access/use them?
Thanks!
BTW, using Ruby 1.9.2-p194 on Ubuntu with RubyMine as the IDE.
Probably wrong. Ruby is a very flexible language, and has what are called open classes. This means that you can open up and change classes at run-time. Doing this on an external library is called monkey patching. So instead of having to replicate all of the code you want to stay consistent, you can just modify the classes and override any methods you want.
A simple example:
class Fixnum
def is_multiple_of_three?
self % 3 == 0
end
end
However, if the changes you want are really significant, it could make sense to fork the gem.
I recommend the bundler gem. This will let you make a Gemfile which lists all of your dependencies. You can list a github repository as a source for the gem, like so:
gem 'gem_name_here', :git => 'git://github.com/username_here/gem_name_here.git'
and then run bundle install to install your dependencies.
If you install the gems with bundler, it acts just like any other gem you have installed.

Using .gemspec version in documentation/library/script

So, I've been writing a bunch of ruby gems recently, and one thing I would find convenient is to include the current version (as specified in the gemspec) in the rdoc-generated documentation for my libraries, and in the OptionParser-generated --help sections used by my scripts (which I'm distributing via gem). Any way I can make it easy for my users to figure out which version of library/script they're currently using.
Is there any way I can access the version I specify in my gemspec from the ruby files in my lib/ or bin/ directories? That way, I don't have to update it multiple places at once - just in my gemspec.
Currently I generate my gemspecs by hand, I haven't jumped on the Rake train yet. If I can't do what I want directly with only what rubygems gives me, would rake or another tool allow me to do this?
There isn't a way, I don't think, because Rubygems is just an installation mechanism for Ruby libraries, which have no predefined notion of versioning.
However, possible workarounds could include using Gem.loaded_specs to find which gems have been loaded:
Gem.loaded_specs["mygemname"].version.to_s #=> "1.2.3"
You might want to take a look at newgem gem - as it both generates appropriate directory layout and also a gemspec file (compatible with GitHub).

Resources