Posts Tagged github

Gem dev tutorial: Adding Rails 3 Engines to Jeweler

If you are a Ruby or Rails developer, chances are you will run into a situation where you want to modify a gem that is on github.  In this tutorial I will show you a real-world example of how I do this, using Jeweler as an example.

I like Jeweler.. I like it alot.. but I always end up creating a gem with it and then tweaking a bunch of files to turn it into a Rails 3 Engine. So I am attempting to integrate Rails 3 engines into Jeweler by adding a command line argument that will tell jeweler to create the engine directories and add the engine file templates.

Fork The Repo

First, I need to fork the repo so I can add my changes. I just login to my github account and go to the jeweler page and click the fork button. Now I have a repo under my account. Then I clone the repo on my dev box. In order to pull changes from the original repo when anything changes, I will need to add a remote upstream:

#clone my forked repo
git clone git@github.com:johnmcaliley/jeweler.git
#add upstream that points to original repo
git remote add upstream git://github.com/technicalpickles/jeweler.git

Test Driven Development

I see that this project uses Cucumber for many of its tests. This time I decide to be a good boy and do some TDD from the get-go. The first thing I want to do is create a cuc feature and write 2 scenarios. The first is a scenario without the –rails3_engine argument. The second is with the argument. I used some of the steps from the cucumber.feature in order to get up and running.

Feature: generating rails3 engine directories and files
In order to use rails 3 engines with jeweler generators
A user should be able to
generate a project setup and specify the rails3_engine option

Scenario: sans rails3 engine setup
Given a working directory
And I have configured git sanely
And I do not want to create a rails 3 engine
When I generate a project named ‘the-perfect-gem’ that is ‘zomg, so good’
Then a file named ‘the-perfect-gem/app’ is not created
And a file named ‘the-perfect-gem/app/controllers’ is not created
And a file named ‘the-perfect-gem/app/helpers’ is not created
And a file named ‘the-perfect-gem/app/models’ is not created
And a file named ‘the-perfect-gem/app/views’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ is not created
And a file named ‘the-perfect-gem/lib/generators’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties/tasks.rake’ is not created

Scenario: Rails 3 engine setup
Given a working directory
And I have configured git sanely
And I want to create a rails 3 engine
When I generate a project named ‘the-perfect-gem’ that is ‘zomg, so good’
Then a file named ‘the-perfect-gem/app’ is created
And a file named ‘the-perfect-gem/app/controllers’ is created
And a file named ‘the-perfect-gem/app/helpers’ is created
And a file named ‘the-perfect-gem/app/models’ is created
And a file named ‘the-perfect-gem/app/views’ is created
And a file named ‘the-perfect-gem/lib/the-perfect-gem’ is created

And a file named ‘the-perfect-gem/lib/generators’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties’ is created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties/tasks.rake’ is created
And ‘the-perfect-gem/lib/the-perfect-gem/railties/tasks.rake’ is blank

And a file named ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ is created
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ requires ‘the-perfect-gem’
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ requires ‘rails’
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ has a module based on the class name
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ subclasses rails engine
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ has placeholders for initializers and rake task inclusion

These scenarios have some steps that are not defined, so obviously they will fail. I need to add the missing step definitions.

##features/step_definitions/generator_steps.rb

Given /^I do not want to create a rails3 engine$/ do
  @rails3_engine = false
end

Given /^I want to create a rails3 engine$/ do
  @rails3_engine = true
end

# one line added here also:
When /^I generate a (.*)project named '((?:\w|-|_)+)' that is '([^']*)' and described as '([^']*)'$
#....
  @use_cucumber ? '--cucumber' : nil,
  @rails3_engine ? '--rails3_engine' : nil, # I added this
#....
end

Then /^'(.*)' has a module based on the class name$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match %Q{module ThePerfectGem}, content
end

Then /^'(.*)' is blank$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match "", content
end

Then /^'(.*)' subclasses rails engine$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match "class Engine < Rails::Engine", content
end

Then /^'(.*)' has placeholders for initializers and rake task inclusion$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match "#rake_tasks do", content
  assert_match '#load "the-perfect-gem/railties/tasks.rake"', content
  assert_match "#initializer 'the-perfect-gem.helper' do |app|", content
  assert_match "#ActionView::Base.send :include, ThePerfectGemHelper", content
end

Then /^'(.*)' requires the engine$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match 'PATH = File.dirname(__FILE__) + "/the-perfect-gem"', content
  assert_match 'require "#{PATH}/engine.rb"', content
end

Dig Into The Existing Code

Next I need to figure out how this thing works and add my code.

The gem contains a binary file under /bin that includes the jeweler libraries and then fires off a generator that accepts arguments

require 'jeweler/generator'
exit Jeweler::Generator::Application.run!(*ARGV)

The run class method is in lib/generator/application.rb. It does some checks on the options and then creates an instance of Jeweler::Generator and calls its run method

generator = Jeweler::Generator.new(options)
generator.run

Make changes to generator.rb

Taking a closer look at the initialize method, I see that I will need to add something for the “rails3_engine” argument that I pass to the jeweler binary. I will add something similar to the cucumber option

self.should_use_cucumber    = options[:use_cucumber]
self.should_create_rails3_engine = options[:rails3_engine] # added this code

I also need to add an attribute accessor so the object can set this instance variable:

attr_accessor   # a bunch of existing attr_accessors listed here.. append mine to end of list
,:should_create_rails3_engine

I think that will do for the initialize stuff. Next I look at the run instance method. It has a method called create_files. I will need to add a condition to this method so Jeweler can check for my rails3_engine option and create the appropriate files. Again I look at the cucumber option for inspiration, since it creates directories in the gem and adds files.  Similar to the cucumber condition, the “rails3_engine” option will create some directories and output my engine template files into these directories.

if should_use_cucumber
  mkdir_in_target           features_dir
  output_template_in_target File.join(%w(features default.feature)), File.join('features', feature_filename)

  mkdir_in_target           features_support_dir
  output_template_in_target File.join(features_support_dir, 'env.rb')

  mkdir_in_target           features_steps_dir
  touch_in_target           File.join(features_steps_dir, steps_filename)
end

# here is what I added
if should_create_rails3_engine
  mkdir_in_target           app_dir
  mkdir_in_target           controllers_dir
  mkdir_in_target           models_dir
  mkdir_in_target           helpers_dir
  mkdir_in_target           views_dir
  mkdir_in_target           main_dir
  output_template_in_target File.join('rails3_engine','engine_template.erb'), File.join(main_dir,'engine.rb')
  mkdir_in_target           generators_dir
  mkdir_in_target           railties_dir
  touch_in_target           File.join(railties_dir,'tasks.rake')
  output_template_in_target File.join('rails3_engine','lib_engine_template.erb'), File.join(lib_dir,lib_filename)
end

I also need to add instance variables for the directories that are used in the rails3_engine option condition. Again, I look at what was coded for cucumber:

#check for a dash in the project name and convert to underscore so classify understands it
def project_class
  self.project_name.gsub("-","_").classify
end

def app_dir
  'app'
end

def controllers_dir
  "#{app_dir}/controllers"
end

def models_dir
  "#{app_dir}/models"
end

def views_dir
  "#{app_dir}/views"
end

def helpers_dir
  "#{app_dir}/helpers"
end

def main_dir
  "#{lib_dir}/#{self.project_name}"
end

def generators_dir
  "#{lib_dir}/generators"
end

def railties_dir
  "#{main_dir}/railties"
end

Since I use the pluralize method in “project_class” I need to include active_support inflectors in the Jeweler class

require 'git'
require 'erb'
require 'active_support/inflector' # I added this one
require 'net/http'
require 'uri'

Add code to options.rb

One part that I notice is missing while looking at how cucumber is set up is the code in lib/jeweler/generator/options.rb.

o.on('--cucumber', 'generate cucumber stories in addition to the other tests') do
  self[:use_cucumber] = true
end

# I added this
o.on('--rails3_engine', 'generate rails3 engine directories and files') do
  self[:rails3_engine] = true
end

Add the file templates

generator.rb makes a few calls with methods like this:

output_template_in_target File.join('rails3_engine','engine_template.erb'), File.join(main_dir,'engine.rb')

I created a directory called “lib/jeweler/templates/rails3_engine” that will house my template files. Then I add 2 erb templates that will generate the source code files dynamically in the gem.
Here are the files:

##engine_template.erb
require "<%=require_name%>"
require "rails"

module <%=project_class%>
  class Engine < Rails::Engine
      #rake_tasks do
        #load "<%=require_name%>/railties/tasks.rake"
      #end

      #initializer '<%=require_name%>.helper' do |app|
        #ActionView::Base.send :include, <%=project_class%>Helper
      #end
  end
end
##lib_engine_template.erb
PATH = File.dirname(__FILE__) + "/<%=require_name%>"
require "#{PATH}/engine.rb"

The first file creates the engine.rb which is necessary to make our gem behave as a Rails 3 engine plugin. Using erb allows me to insert text dynamically based on the project name and the require directory which were set in generator.rb instance methods. The second file is just a simple template that requires engine.rb in the project.

Fire off them tests man!

This tutorial does not reflect what I actually did in development.  I run my tests first, look at the failures, write code, refactor, then repeat until everything is green as a cuc.  But to simplify, I am just showing that you need to run tests after you write your code.  Jeweler uses a rake task to execute all the cuc scenarios, so I run this command:

rake features
103 scenarios (103 passed)
652 steps (652 passed)

Now I can see all of my scenarios as well as existing scenarios are all passing. Good to go!

I also updated the unit test, test_options.rb.  But I am getting some errors relating to ‘rr’ -> “NotImplementedError: super from singleton method that is defined to multiple classes is not supported; this will be fixed in 1.9.3 or later”.  I don’t think I will be able to run these tests until I get this resolved.  Looks like it is an issue with ‘rr’ and ruby 1.9.2.  I might try downgrading ruby using RVM and running from there.

Test with a live app

After the cucumber tests are passing, I want to look at this in a real Rails 3 app.  So I create a new gem using my jeweler fork. I just execute the jeweler bin from my fork:

>cd jeweler
>bin/jeweler myengine --rails3_engine
create  .gitignore
create  Rakefile
create  Gemfile
create  LICENSE.txt
create  README.rdoc
create  .document
create  lib
create  lib/myengine.rb
create  test
create  test/helper.rb
create  test/test_myengine.rb
create  app
create  app/controllers
create  app/models
create  app/helpers
create  app/views
create  lib/myengine
create  lib/myengine/engine.rb
create  lib/generators
create  lib/myengine/railties
create  lib/myengine/railties/tasks.rake
create  lib/myengine.rb
Jeweler has prepared your gem in myengine

I add a controller and a view to the gem, create a gemspec and install my gem. Then I add the gem to a rails 3 app Gemfile (using :path=>/path/to/my/gem), bundle install, fire up the app server and take a look at the view that I added.

It appears, so I know that the Engine was initialized in the app. Good to go!

Here is the fork if you want to look at the code:
https://github.com/johnmcaliley/jeweler

Tags: , , , , , ,

Working with private RubyGems in Rails 3

I have several gems that contain proprietary code that I do not want public to have access to.    Rubygems.org is great for gem hosting, but they do not allow security restricted gems, since it defeats the purpose of having an open community for ruby gems.  There are several options if you want to host your own gems and restrict access to them.  Here are some of the options that I considered

1. Install gemcutter and use that for a gem server.

2. Install the geminabox sinatra app.  This is a lightweight gem server with a web interface.

3. Use github.  They no longer build gems, but you can still install from github using Bundler (or a plain rake task).

I started playing around with the first 2 options, but I ultimately decided to just use the github solution.  Why waste resources setting up a private server when my code is already stored on github with security.  I am assuming you know how to set up a project on github, password protect it and push your gem source to github.

Git’r tagged

Probably one of the coolest parts of working with gems is being able to install specific versions of a gem in your project.. which is much easier than using vendor plugins.  In order to keep track of your versions you need to work with git tags.  You could also do this with branches, but it would get a bit hairy if you created a new branch for every patch version.  A good example of the proper way to set this up is the Ruby on Rails source on github – > http://github.com/rails/rails.  Click the drop-down where it says “Switch Branches”.  You can see they have a branch for every minor & major version 1.2, 2.0, 2.1, 2.2, 2.3, and 3.0 (master).  But notice that you do not see branches for minor versions like 2.3.8.  They use git tags to keep track of each version instead.  Click “Switch Tags” on the rails source github page.  Here you can see they created a separate tag for each version.  You will want to do the same with your private gems that you host on github.

Jeweler to the rescue

Luckily, jeweler has already done most of the work for us.  Install jeweler first:

gem install jeweler

Jeweler provides some nice shortcuts for creating, building and installing your gems. Take a look at the documentation for more info. I am only going to touch on the versioning that is included with this gem. After you create a project with Jeweler, you get a rakefile that gives you building and versioning tasks. Lets say you make a bunch of changes in your gem project, commit and push them to github and you are ready to bump this gem to a new version. You can use these Jeweler rake tasks to accomplish that:

rake version:bump:patch
#you can also use version:bump:minor and version:bump:major.. 1.2.3 -> 1=major, 2=minor, 3=patch
rake github:release
rake git:release

Jeweler also has a rake task called “release”. This will accomplish the 3 tasks above, but it also tries to push the gem to gemcutter. I could not find a way to turn that off in the documentation (although it may be possible.. I have not looked at the source in depth). I opted to write some custom tasks that combine these 3 tasks into a single task. So I added this to my rakefile:

namespace :version do
  desc "create a new version, create tag and push to github"
  task :github_and_tag do
    Rake::Task['github:release'].invoke
    Rake::Task['git:release'].invoke
  end

  task :patch_release do
    Rake::Task['version:bump:patch'].invoke
    Rake::Task['version:github_and_tag'].invoke
  end

  task :minor_release do
    Rake::Task['version:bump:minor'].invoke
    Rake::Task['version:github_and_tag'].invoke
  end

  task :major_release do
    Rake::Task['version:bump:major'].invoke
    Rake::Task['version:github_and_tag'].invoke
  end
end

The first tasks takes care of the git tag and github commit/push. The other 3 tasks use the first task and add the version bump for major, minor or patch. So now if I want to do a new patch version, I just have to execute one rake task.

rake version:patch_release

That is half of the equation. The other half is installing the gem from github in your Rails 3 project with Bundler.

Bundler takes care of the rest

Bundler ships with Rails 3 and its purpose is gem dependency management. I really like this tool so far, and the development seems to be moving along very fast. I learned a little trick when I was trying to see if something was fixed in Rails Edge. You can specify github as the source of the gem and it will pull the source code from github, build it and install it for you. You can even specify the tag which makes life even easier. So all I have to do is add one line to the Gemfile in my Rails 3 project and install the bundle. Check this out:

Gemfile

gem ‘mygem’, :git => ‘git@github.com:myuserid/mygem.git’, :tag=>”v1.0.2″
bundle install

Now that is easy!

Tags: , , , , ,

Git Tutorial Part 1: Working with branches and release versions of your Rails app

This week I am working with branches in git for the first time.  Up until now I just used my master branch for everything which works ok since I am the only developer on most of my apps, but now I realize branches will make my life alot easier in more than a few situations.  I am working with my brother on a project where it is necessary for us to use branches, so he started me on the path to git branch knowledge.  Here is my workflow so far.. and most of this relates to working with a Ruby on Rails web app that has stable milestone releases and a master branch for the future version.

I guess lets start with the basics of initializing git and using Github to host your code.  I am assuming you have git installed and you are using Github to host your main repository.. if no, proceed to Google.  Also, I am assuming you already have a project.. Rails or other.

ONE BIG NOTE: REALIZE THAT JUST BECAUSE YOU CREATE A BRANCH OR MAKE A CHANGE AND COMMIT IT ON YOUR LOCAL MACHINE, THAT DOES NOT MEAN THAT IT SENDS IT TO GITHUB OR THE MAIN REPOSITORY!!!  I WAS TOTALLY CONFUSED ABOUT THAT THE FIRST TIME I USED GIT.  LEARN TO PUSH AND PULL!

Go to your project directory, initialize git, add all the files and then make your first local commit with a message

cd /your_app
git init
git add *
git commit -m 'initial commit'

Now you are set up with your local repo. Next you need to login to (or create) your github account. You will create a new repository and name it the same thing as your local app. They will give you instructions similar to what I am telling you here after it is set up. This will add an origin to your local git config and then push all the local files that you just committed to that origin. If you have problems getting this to work, take a look in the github help about security and setting up your ssh keys here

git remote add origin git@github.com:your_username/your_app.git
git push origin master

You will get the following output

Counting objects: 22, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (19/19), done.
Writing objects: 100% (22/22), 3.73 KiB, done.
Total 22 (delta 3), reused 0 (delta 0)
To git@github.com:your_username/your_app.git
* [new branch] master -> master

What you see here is that your files were transferred to github and a new branch was created called “master”. The new branch was created automatically since there were no branches in this github project. To see what branches you have available on your local machine type:

git branch
* master

Your local machine knows about one branch and it is called master. Lets do some more branching now. My project is already at Version 1.0 but I am just now adding it to Github. Version 1.0 is stable and I may or may not make fixes or small additions to it. I am planning out my development roadmap and some major changes are going to happen in V2. So I don’t want to have to worry about changing and adding a bunch of crap and then realize that I need a minor fix on V1.. that would be a mess because I would have a codebase that was moving toward V2 and I would have to hack and hack in order to deploy that small change back to version 1.. what a mess. Thats where branching comes in. We need a Version 2 branch so we can keep things separate. To create another branch on your local machine do this:

git branch version-1_0-stable

Lets see if it worked..

git branch

OUTPUT:

* master
  version-1_0-stable

Yep.. there is our new branch. But notice that we are still on the master branch. Git puts a asterisks beside the current branch. In order to switch branches on our local machine, we need to use the checkout command.

git checkout version-1_0-stable

OUTPUT:

Switched to branch 'version-1_0-stable'

If you run “git branch” again, you will see a star beside version-1_0-stable. Wouldn’t it be easier if you could branch and switch all in one command? Oh yeah.. you can. Why didn’t I just say that in the first place?? Who knows, why I do alot of stuff… Here is how you delete your branch and then create it with the one-liner. Note that you can’t delete a branch you are currently on, so you must checkout back to master first:

git checkout master
git branch -D version-1_0-stable
git checkout -b version-1_0-stable

By throwing in that -b flag, you are telling git to create the branch and then switch to it all in one move.

Ok, cool, so I got another local branch. Why don’t I see the new branch in Github?? Because I have been working with git branches locally. Github is not aware of these changes because I have not pushed anything to it. You can see what github has by logging in and clicking on the “Switch Branches” dropdown in your project or by using the -r flag on your local box -> “git branch -r”. So now we need to tell Github about this branch.

git push origin version-1_0-stable

OUTPUT:

Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:your_username/your_app.git
 * [new branch]      version-1_0-stable -> version-1_0-stable

Frickin awesome dude… I gots a new branch and github has it too. So I have 2 branches that I can work in separately. Most of my work will be in the master branch which is on the path to version 2, but if something craps out in V1 and I have to put in a bug fix, I just “git checkout version-1_0-stable”, make my changes, commit them, and push them to the the V1 branch. Then I could easily deploy the new code without worry about V2 code getting in the way. Lets try that out:

You are already using version-1_0-stable. So go to your editor of choice and change a file. You must commit the changes locally first. This will commit to only the current branch, so master will not get these changes since you are on version-1_0-stable. You can commit your files several ways. Here are the 2 ways I do it.
1. Commit all local changes at the same time and use -v to view the changes

git commit -a -v

2. Commit a single file if you have other outstanding changes that you don’t want committed yet. Just use the relative path to a single file (or * wildcard it for a whole dir).

git commit -v path/to/your/file

Now your local machine knows about the committed files, but not Github. You have to push.

git push

OUTPUT:

warning: You did not specify any refspecs to push, and the current remote
warning: has not configured any push refspecs. The default action in this
warning: case is to push all matching refspecs, that is, all branches
warning: that exist both locally and remotely will be updated.  This may
warning: not necessarily be what you want to happen.
warning:
warning: You can specify what action you want to take in this case, and
warning: avoid seeing this message again, by configuring 'push.default' to:
warning:   'nothing'  : Do not push anything
warning:   'matching' : Push all matching branches (default)
warning:   'tracking' : Push the current branch to whatever it is tracking
warning:   'current'  : Push the current branch
Counting objects: 14, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (7/7), done.
Writing objects: 100% (9/9), 815 bytes, done.
Total 9 (delta 5), reused 0 (delta 0)
To git@github.com:your_username/your_app.git
   d8b9507..862d326  version-1_0-stable -> version-1_0-stable

Look at all those warnings. WTF is git talking about?? They want us to type more options in?? Well.. I actually think this is crap, but there is probably a good reason behind the default behavior. By default it is going to push all branches that it finds on your local box over to the origin server. So for instance, if you change and commit something on master, switch branches to V1 and change and commit something on that branch too, then git will push both of them. That could potentially be dangerous if you were not ready to push master or V1. So in order to fix that you need to pass your git config some options to make it only push the current branch you are working in.

git config push.default current

If you look at /your_app/.git/config, then you will see it added some options to your config.

[push]
        default = current

Next time you issue the git push command the warning goes away and it only pushes the current branch. Thanks for not being an asshole this time git… I really appreciate it.

So we are looking pretty good, but there is one more thing that is bugging me. What if I make a change to V1 and I want the changes to show up in V2 also. The bug is most likely in V2, unless I removed that feature or completely re-coded it. So how do I get them darn changes over to me other branch?? Move back to master (V2), maybe check the differences so you don’t totally mess it up, and then issue the merge command:

git checkout master
git diff master version-1_0-stable
git merge version-1_0-stable

If you have any conflicts, the merge might not work and it will tell you to fix them. Here is the message “Automatic merge failed; fix conflicts and then commit the result.” So how do I fix the conflicts? Go to the files that it marked as conflicting. In my case, my LICENSE file was conflicted so it said:

Auto-merging LICENSE
CONFLICT (content): Merge conflict in LICENSE

So I look at the file and git has put some junk in there that shows where the conflicts are and which branch they are coming from. Just edit the file to make it work and get rid of the junk (<<<<<<<<<<).

<<<<<<< HEAD:LICENSE Copyright (c) 2010 John McAliley  ======= Copyright (c) 2009 John McAliley >>>>>>> version-1_0-stable:LICENSE

Hmm.. its 2010, the file should say that, so I edit and make it look like this:

Copyright (c) 2010 John McAliley

Then I do a “git commit -a” and the conflict has been resolved. If you issue a “git merge version-1_0-stable” then it will tell you its up to date. Cool, I guess we are done. Not so fast chief!!! Don’t forget to push it to Github

git push

Now we done! The change in V1 is also in V2. Go drink a beer. You deserve it.

I think that about covers my workflow as it stands now aside from pulling using the rebase command. Yehuda Katz has an excellent tutorial on his Git workflow regarding pulling, pushing and resolving conflicts (although he does not go into branching). I have only been doing branching and versioning in git for a week, so I am sure some of you git masters might have a better workflow. If so, don’t be stingy.. leave a comment and show people a better way. Next tutorial will cover tagging as related to minor version changes in the master before it is actually considered stable. Check out the ruby on rails source in github if you can’t wait. They use branches and tagging for major and minor releases (including betas in the master branch)

Tags: , , , , ,