This is a first part of my Ruby Tricks & Quirks serie. I’ll present some non-obvious techniques available in beloved Ruby.
As you probably know in Ruby one can inspect almost everything. Available variables, methods or constants to name a few. But is it possible to check the number of method arguments? Sure it is! Hey, it’s ruby impossible is nothing ;)
The trick is achieved by using arity method.
class Logger
def log(message)
$stderr.puts message
end
end
puts Logger.instance_method(:log).arity # => 1
The most popular use of arity is to check the number of arguments accepted by block.
proc { |a, b, c| a + b + c }.arity # => 3
However now we know that arity applies to methods as well.
Let’s digg deeper and see what does arity return for blocks and variable length of arguments.
class Logger
def initialize(stream, *args)
@stram = stream
@args = args
@silent = false
end
def log(*messages)
@stream.write messages.join("\n") unless self.silent
end
def silence(&block)
old_silent = @silent
@silent = true
yield
ensure
@silent = old_silent
end
end
(Logger.instance_methods - Object.instance_methods).each do |method|
puts "#{method} takes #{Logger.instance_method(method).arity} arguments"
end
Output of the code above:
silence takes 0 arguments log takes -1 arguments initializer takes -2 arguments
- block doesn’t count as argument so arity of silence equals 0
- log method has variable length of arguments and it’s arity equals -1
- if method accepts both mandatory arugments and varaible length of them then
- arity equals (-1 - NUMBER_OF_MANDATORY_ARGUMENTS). In that case arity of initialize equals -2
automatic_foreign_key 1.2 released
23 Jan 2011
AutomaticForeignKey allows you to create foreign keys easier than ever.
Particularly lots of migrations doesn’t need any change. Let’s dive into it quickly.
class CreateGroups < ActiveRecord::Migration
def self.up
create_table :groups do |t|
t.string :name, :null => false, :limit => 50
t.integer :user_id, :null => false
t.timestamps :null => false
end
end
def self.down
drop_table :groups
end
end
In that case we’ll have foreign key on groups(user_id) referencing users(id).
But wait a moment. This one looks like ordinary migration. Where did you define foreign key? Well, automatic_foreign_key is smart enough to be predict that user_id column should be referenced to users(id).
Obviously we could customize the behaviour.
# force user_id to reference admins table
t.integer :user_id, :null => false, :references => :admins
# don't create foreign key on application_id column
t.integer :application_id, :references => nil
It’s dead easy, isn’t it?
If you want to see more just go to github page or start using it now!
$ gem install automatic_foreign_key
Let’s take a look at what brings version 1.2 to us.
Rails 3 generator
New generator creates automatic_foreign_key initializer:
$ rails g automatic_foreign_key:install create config/initializers/automatic_foreign_key.rb
Currently 3 options are supported:
- on_update - default ON UPDATE action (available options are :cascade, :restrict, :set_null, :set_default, :no_action)
- on_delete - default ON DELETE action (available options as above)
- auto_index - automatically create indexes on foreign keys
Fixed auto-index when restoring schema
Previously restoring schema may have led to duplicated indexes. Currently the problem is solved.
Upgraded to RSpec 2
Specs are compatible with Rspec 2.4.0.
Bundler instead of Jeweler
Gemspec is maintained by bundler. You can read more about this change on my blog post.
Crafting gems. Jeweler vs Bundler
22 Jan 2011
Recently I’ve changed some of my gems to use bundler in favor of jeweler. In short both make generating gemspec easier (obviously bundler can do way more than that). Actually I use bundler anyway so why have another dependency in form of jeweler? To compare both let’s take a look at how they work for us. We’ll use jeweler 1.5.2 and bundler 1.0.7.
Creating files structure
$ jeweler mygem create .gitignore create Rakefile create Gemfile create LICENSE.txt create README.rdoc create .document create lib create lib/mygem.rb.rb create test create test/helper.rb create test/test_mygem.rb.rb
Creating basic structure is actually the biggest strength of jeweler.
$ jeweler --help
Typing that will list dozens of available options. Choosing test framework, initializing git repo or specyfing github credentials to name a few.
How that compares to bundler?
$ bundle gem mygem create mygem/Gemfile create mygem/Rakefile create mygem/.gitignore create mygem/mygem.gemspec create mygem/lib/mygem.rb create mygem/lib/mygem/version.rb Initializating git repo in /home/snatcher/temp/garbage/mygem
Slighty less files were created. We don’t have LICENSE and test framwork files included by default. List of available options is also very thin.
$ bundle help gem -b, [--bin=Generate a binary for your library.] [--no-color=Disable colorization in output] -V, [--verbose=Enable verbose output mode]
Bundler’s approach is to generate required files only. Tasks not related directly to gemspec are up to developer.
Handling gem version
Jeweler prefers to keep version number in text file named VERSION
$ cat VERSION 0.0.1
On the other hand bundler creates version.rb file and assignes number to VERSION constant
module Mygem
VERSION = "0.0.1"
end
In my opinion bundler approach is way better as gem version is available always when the code is loaded.
puts Mygem::VERSION # => 0.0.1
Of course using Jeweler we’re also able to have version as constant but bundler gives us it for free.
Gem metadata
With jeweler things like summary, description and author are written to Rakefile. Basing on data in Rakefile jeweler creates gemspec. In that case gemspec is meant to readonly and we put all metadata into Rakefile. It leads to duplication as we have the same parts in both Rakefile and gemspec.
Jeweler::Tasks.new do |gem|
gem.name = "redhillonrails_core"
gem.summary = %Q{Adds support in ActiveRecord for foreign_keys
and other database-related stuff}
gem.email = "michal.lomnicki@gmail.com"
gem.homepage = "http://github.com/mlomnicki/redhillonrails_core"
gem.authors = ["Michał Łomnicki"]
end
To generate gemspec rake task needs to be invoked
$ rake gemspec Generated: redhillonrails_core.gemspec redhillonrails_core.gemspec is valid.
Bundler prefers slightly different approach. We edit gemspec itself
Gem::Specification.new do |s|
s.name = "exceptioner"
s.version = Exceptioner::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Michał Łomnicki"]
s.email = ["michal.lomnicki@gmail.com"]
s.homepage = "https://github.com/mlomnicki/exceptioner"
s.summary = "Stay notified of exceptions raised by
your application."
end
No additional work is needed. Gemspec is first class citizen of our gem.
Dependencies
Jewler encurages us to use bundler so we define dependencies using beloved Gemfile.
source :rubygems
gem "activerecord"
group :development, :test do
gem "jeweler", "~> 1.5"
gem "rspec", '~> 1.3'
gem "pg"
gem "mysql"
end
That’s very familiar technique for everyone who is used to bundler.
On the other hand content of Gemfile created by bundle gem command is somehow surprising.
source "http://rubygems.org"
gemspec
So what is a preferred place to set gem dependencies? Actually bundler follows its philosophy and encurages us to put dependencies in gemspec file.
Gem::Specification.new do |s|
# metadata goes here
s.add_dependency("mail", ["~> 2.2"])
s.add_dependency("xmpp4r", ["~> 0.5"])
s.add_development_dependency("rack")
s.add_development_dependency("mocha")
end
Invoking bundle install will still work as expected.
$ bundle install Using rake (0.8.7) Using activesupport (3.0.3) Using i18n (0.5.0) Using mime-types (1.16) Using polyglot (0.3.1) Using treetop (1.4.9) Using mail (2.2.14) Using xmpp4r (0.5) Using exceptioner (0.0.5) Using mocha (0.9.10) Using rack (1.2.1) Using bundler (1.0.7) Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
Releasing
Both jeweler and bundler has similar functionality here.
$ rake release
Invoking the task above will:
- build a gem from gemspec
- tag and push code to the repo
- push gem to rubygems
Summary
Both jeweler and bundler do great job doing tedious work for us. Bundler is younger brother of jeweler which introduced some very good improvements. Jeweler is still more powerful and flexible. Bundler is thinner and relies more on gemspec itself. Personally I’ve changed from Jeweler to Bundler and I’m pretty satisfied with that.