Create a Ruby Gem for a jQuery Plugin: Advanced

Share this article

Screenshot 2014-10-12 11.39.13

In the previous article we created a gem from scratch that pulls in a jQuery plugin and adds it to a Rails application. In this post, let’s look into some advanced concepts to make our gem even better.

We will be using same gem and Rails application used in the previous post.

Key Takeaways

  • Creating a custom helper for a gem can enhance its usability and user experience, as demonstrated by the creation of a ‘zoom_image_tag’ for the ImageZoomer module.
  • It is crucial to ensure that a gem is extensible, allowing developers to modify default configurations. This can be achieved by creating a Configuration class and using an initializer method.
  • Publishing a gem is simplified by Bundler’s tasks, which help package and distribute gems. Once a Rubygem account is created, the gem can be compiled into a .gem package and uploaded to Rubygems.
  • Contributing to open source projects, such as creating and sharing a gem, is a rewarding experience and a great way to enhance one’s development skills. It’s also a way to contribute to the wider developer community.

Custom Helper

So far, we have successfully applied the zoom effect to images by simply adding the image_zoomer CSS class. But, don’t you think our gem deserves a custom helper? I mean, wouldn’t it be better to use our own helper tag and free the user from haveing to add the image_zoomer CSS class?

Let’s create a custom helper called zoom_image_tag. Images added with zoom_image_tag will have the zoom effect applied, by default. Create a file called zoom_image_helper.rb under lib/image_zoomer directory in the gem and paste following code:

module ImageZoomer
  def zoom_image_tag(*args, &block)
    options = insert_zoom_class(args.extract_options!)
    args << options
    image_tag(*args, &amp;block)
  end

  def insert_zoom_class(options)
    class_name = "image_zoomer"
    if options.key?(:class)
      options[:class] += " #{class_name}"
    elsif options.key?('class')
      options['class'] += " #{class_name}"
    else
      options[:class] = class_name
    end
    options
  end
end

We have defined two methods on the ImageZoomer module. The zoom_image_tag method receives multiple arguments and a block just like the image_tag helper.

options = insert_zoom_class(args.extract_options!) is calling the extract_options! method provided by ActiveSupport. It is used to extracts options from a set of arguments. It removes and returns the last element in the array if it’s a hash, otherwise it returns a blank hash. The returned hash is passed to insert_zoom_class.

insert_zoom_class is a straightforward method used to insert the class into the provided hash. The key could be a symbol, string, or not provided at all. Here, the image_zoomer class attribute is returned as part of the hash.

args < < options appends the options hash to args. Since extract_options! removes the options from args, we have to append them back.

image_tag(*args, &block) is the standard Rails image_tag helper with our modified arguments. The takeaway here is zoom_image_tag is simple a wrapper for image_tag. Users of our gem now have a convenience method to make their lives easier.

Navigate to the test Rails app and open app/views/site/index.html.erb under your Rails app and replace image_tag with:

<%= zoom_image_tag("test.jpg"); %>

Start the web server and open http://localhost:3000/site/index in the browser. You will see an error stating something like undefined method 'zoom_image_tag'. What is happening?? We didn’t tell Rails that zoom_image_tag is a helper method. Let’s do this now.

We know that image_tag helper is defined in the ActionView::Helpers::AssetTagHelper module. We can simply reopen this modules and add the ImageZoomer module. Open lib/image_zoomer.rb (in the gem) and add:

require "image_zoomer/zoom_image_helper"

module ActionView
  module Helpers
    module AssetTagHelper
      include ImageZoomer
    end
  end
end

Now, zoom_image_tag will become part of the ActionView::Helpers::AssetTagHelper methods, joining the league of other helpers such as image_tag, javascript_include_tag, etc.

Restart your web server and refresh the page. The image zooming effect is applied using our new helper. Cool.

An Important Refactoring

Including ImageZoomer in an existing module is a form of Monkey Patching, and is considered harmful. Luckily, Rails provides an elegant way of adding custom helpers, called Railties, that doesn’t use Monkey Patching. Railties provide several hooks to participate in the initialization process. Read more about Railtie here.

First, remove the newly inserted code from lib/image_zoomer.rb so it consists only of the three require statements. Create a file railtie.rb in the lib/image_zoomer/ directory with:

class Railtie < Rails::Railtie
  initializer "image_zoomer.initialize_zoom_image_helper" do |app|
    ActiveSupport.on_load(:action_view) do
      include ImageZoomer
    end
  end
end

Here is our Railtie class that is subclassed from Rails::Railtie as per the Rails documentation. The call to the initializer method takes a name and a block. The block does the our magic, calling ActiveSupport.on_load(:action_view) and passing another block to include the ImageZoomer module. Our ImageZoomer module will only be included when ActionView is fully loaded. This is the standard way of adding helpers since Rails 3. Read more about this here.

Require railtie.rb in lib/image_zoomer.rb with the following code:

require "image_zoomer/railtie"

Save your changes, restart the web server, notice that the zoom effect is still working. Now, zoom_image_tag is being inserted by Rails built-in hooks and we have eliminated Monkey Patching.

Our final version of lib/image_zoomer.rb looks like this:

require "image_zoomer/version"
require "image_zoomer/engine"
require "image_zoomer/zoom_image_helper"
require "image_zoomer/railtie"

Overriding Default Configurations

Our gem is working fine and we could call it “done”, but is it really extensible? By “extensible” I mean, can a developer change the default configuration of the jQuery plugin? By default, the height and width of the zoom lens is 90px and zoom-level is 1.5. If a developer is not able to update these settings, our gem is not extensible. In other word, we aren’t done yet.

Let’s add some extensibility so that developers can easily override default settings.

Configuration Class

Create a class to hold our configuration in a file called lib/image_zoomer/configuration.rb in your gem and add:

module ImageZoomer
  class Configuration
    attr_accessor :width, :height, :zoom_level

    def initialize
      @width = 90
      @height = 90
      @zoom_level = 1.5
    end
  end
end

We have simply created a class in our ImageZoomer module. The Configuration class has three properties: width, height and zoom_level. Instancs of this class will get default value that match those defined by the jQuery plugin.

Require configuration.rb in lib/image_zoomer.rb:

require "image_zoomer/configuration"

The best place to create an instance of this class is in lib/image_zoomer/railtie.rb. Add following code to our Railtie class:

initializer "image_zoomer.configuration" do |app|
  app.config.image_zoomer = ImageZoomer::Configuration.new
end

We are, again, calling the initializer method and passing it a name and a block. app here is represents Rails.application and app.config is an instance of Rails::Application::Configuration used to hold all configuration for the app. We have created an instance of ImageZoomer::Configuration and assigned it to app.config.image_zoomer.

Now, accessing Rails.application.config.image_zoomer will present an instance of ImageZoomer::Configuration. Restart your web server, refresh your browser and make sure we haven’t broken anything.

To immediately play with an instance of ImageZoomer::Configuration, launch the console (rails console) and type Rails.application.config.image_zoomer. If everything is setup correctly, you’ll see our ImageZoomer::Configuration in action.

Providing Settings to the Plugin

The gem now has a configuration class, we only need to use it. Create a new file called image_zoomer_options.js.erb in app/assets/javascripts under the gem with:

var __ImageZoomer = {
  options: {
    width: "<%= Rails.application.config.image_zoomer.width %>",
    height: "<%= Rails.application.config.image_zoomer.height %>",
    zoom_level: "<%= Rails.application.config.image_zoomer.zoom_level %>"
  }
}

The above code is pretty simple. Declare a JavaScript variable (__ImageZoomer) and assigning it an object with a property called options. The options object has three properties that correspond with a property from the Rails.application.config.image_zoomer object. This is an erb template because we want to execute some Ruby code, and Sprockets automatically handles execution of dynamic code in asset files. As such, image_zoomer_options.js.erb will point result in /assets/image_zoomer_options.js in the HTML response and will look like:

var __ImageZoomer = {
  options: {
    width: "90",
    height: "90",
    zoom_level: "1.5"
  }
}

As you can see, our dynamic code executed and width, height and zoom_level have default values that we defined in the constructor of ImageZoomer::Configuration.

With our new JavaScript object holding the configuration values, it’s time to modify the jQuery plugin initialization code to use it. Open app/assets/javascripts/image_zoomer_initializer.js under your gem and replace the existing code with:

$(function() {
  if (typeof __ImageZoomer == "undefined") {
    __ImageZoomer = {options: {}};
  }
  $(".image_zoomer").each(function() {
    $(this).image_zoomer(__ImageZoomer.options);
  });
});

First, check if the __ImageZoomer variable exists and create it, if necessary. According to the jQuery Plugin usage, we can pass an object to the image_zoomer method and properties defined in that object will be applied. Passing __ImageZoomer.options applies our settings to change the behavior of image zooming.

Restart theyour web server, refresh the browser, and you should see same image zooming experience. __ImageZoomer points to default values, so there is no change in the zooming experience for the moment. But, we have taken an important step towards giving users the ability to override default settings.

Modifying Default Values in Rails

Rails uses initializers to modify configuration for gems and core Rails settings. We will do so as well. First, there should be an elegant way in ImageZoomer::Configuration to update properties. Open lib/image_zoomer/configuration.rb and add the following method to the class:

# Override default options with the provided ones in block
def set_options(&amp;block)
  block.yield(self)
end

We have created a method set_options which accepts a block that yieldsself (current instance of the configuration). Calling this method with appropriate block will update the default values of Image_Zoomer::Configuration from our Rails.application.config.image_zoomer initializer.

In the Rails app, create config/initializer/image_zoomer.rb with:

Rails.application.config.image_zoomer.set_options do |options|
  options.width = 50
  options.height = 50
end

When this code executes, width and height are changed to 50, and image_zoomer is still 1.5 because we haven’t changed it. Developers can easily modify this block according to their needs. Since we have already setup our jQuery code in such a way that it will pick latest values from our Rails.application.config.image_zoomer we are good to go.

Restart the web server, refresh the browser, and observe different width and height than before.

Fixing a Caveat

Wait, the width and height didn’t change. What’s up? This is due to a caveat.

Sprockets caches assets so that it doesn’t have to process them again and again. When any asset is served the first time, it gets cached and that cache will be served on subsequent requests. This cache will not update, even when we restart the web server. One way to update the cache for any asset is to change its source code, but that could lead to useless changes and silly git commits full of blank spaces/lines. Let’s look at a better way. Open lib/image_zoomer/railtie.rb and change the image_zoomer.configuration block like so:

initializer "image_zoomer.configuration" do |app|
  app.config.image_zoomer = ImageZoomer::Configuration.new
  # Clear the cache
  FileUtils.rm_rf("#{Rails.application.root}/tmp/cache/assets") if Rails.env.development?
end

Sprockets store the caches at a specified path in our Rails app. We are deleting that directory when the code in our lib/image_zoomer/railtie.rb is executed (which is when theserver starts and the gem is loaded). We are using Ruby’s built in methods to delete that folder in the development environment.

Restart the web server and you’ll finally see the zoom lens with different dimensions. Go ahead and change settings in config/initializers/image_zoomer.rb to your heart’s content.

Publishing the Gem

Our gem is ready. Publishing a gem is quite simple, thanks to the Rubygems core team. Bundler has already provided some tasks that are helpful in packaging and distributing gems. First, you must have a Rubygem account. Head over to http://rubygems.org and sign up.

After creating an account, update the relevant information in the image_zoomer.gemspec file. You’ll probably want to push this gem to Github, too.

Navigate to your gem’s root directory and issue the following command:

rake release

This command will compile all the required files into a .gem package, commit thelatest code along (with gem version) as a tag to GitHub, and upload the gem to Rubygems. While publishing you will be asked for your Rubygems credentials, provide those and you are done. Once the gem is successfully published you will see this in your dashboard on Rubygems.

If for some reason rake release fails, you can also build the gem using rake build. If that works, manually push the generated image_name-version.gem file to Rubygems with gem push path/to/image_name-version.gem.

One Thing Left

Now there is only one thing left, and that is to share your gem with your colleagues, friends, and other Rubyists. Don’t forget to shoot an email to Peter Cooper to get it published in RubyWeekly, which is one of the most widely read Ruby newsletters.

Conclusion

Building something and contributing to open source is an awesome experience and every developer should do it. I hope you have learned a lot in these two articles. Go ahead and make a gem for your favorite jQuery plugin.

Frequently Asked Questions (FAQs) about Creating a Ruby Gem with jQuery Plugin

What is the purpose of creating a Ruby Gem with a jQuery plugin?

Creating a Ruby Gem with a jQuery plugin allows developers to encapsulate and distribute reusable code. This can be particularly useful when you want to share your code with other developers or use it across multiple projects. The jQuery plugin enhances the functionality of the Ruby Gem, allowing for more interactive and dynamic web applications.

How do I install the jQuery-rails gem?

To install the jQuery-rails gem, you need to add the following line to your application’s Gemfile: gem 'jquery-rails'. Then, execute the bundle install command in your terminal. This will install the gem into your application.

How can I make my Ruby Gem configurable?

Making a Ruby Gem configurable allows users to set their own preferences and settings. This can be achieved by creating a configuration file within the gem. This file should define a module with a method for each configurable attribute. Users can then set these attributes in their application’s initialization file.

What is the purpose of the jquery-ujs library in Rails?

The jquery-ujs (Unobtrusive JavaScript) library is a set of features added to jQuery that work with Rails to provide functionality such as AJAX calls, form validation, and more. It allows Rails to work seamlessly with jQuery, making it easier to create interactive web applications.

How do I update the version of my Ruby Gem?

To update the version of your Ruby Gem, you need to modify the version number in your gemspec file. After making the change, you can build and publish the new version of your gem using the gem build and gem push commands respectively.

How can I add a jQuery plugin to my Ruby Gem?

To add a jQuery plugin to your Ruby Gem, you need to include the plugin’s JavaScript file in your gem’s assets. You can then require this file in your application’s JavaScript manifest file. This will make the plugin available to your application.

How do I use the config gem in Ruby?

The config gem in Ruby allows you to manage your application’s settings in a simple and organized manner. To use it, you need to add the gem 'config' line to your Gemfile and run bundle install. You can then create a configuration file and define your settings in it.

What are the benefits of using the jQuery-rails gem?

The jQuery-rails gem provides a simple and convenient way to include jQuery in your Rails application. It also includes the jquery-ujs library, which adds additional functionality to jQuery that works seamlessly with Rails. This makes it easier to create dynamic and interactive web applications.

How do I troubleshoot issues with my Ruby Gem?

Troubleshooting issues with your Ruby Gem can involve several steps. First, ensure that you have the latest version of the gem installed. If the issue persists, check the gem’s documentation for any known issues or solutions. You can also reach out to the gem’s community for help.

How can I contribute to the development of a Ruby Gem?

Contributing to the development of a Ruby Gem can involve submitting bug reports, proposing new features, or improving the gem’s documentation. You can also fork the gem’s repository, make changes, and submit a pull request. Always remember to follow the gem’s contribution guidelines.

Imran LatifImran Latif
View Author

Imran Latif is a Web Developer and Ruby on Rails and JavaScript enthusiast from Pakistan. He is a passionate programmer and always keeps on learning new tools and technologies. During his free time he watches tech videos and reads tech articles to increase his knowledge.

GlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week