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, &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(&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 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.