A project I’m working on required a score to be calculated on an ActiveRecord model, and stored in the database.
The User#calculate_score
method calculates the score:
class User def calculate_score 100 end end
In ActiveRecord models, a convention is that methods that update the database should have a bang, or an exclamation point appended to their names.
To add a function that stores the score, we add User#calculate_score!
:
class User def calculate_score 100 end def calculate_score! update_attribute!(:score, calculate_score) end end
This introduces some duplication, which can be removed with some trickery.
A generic helper function named #bang
can take care of the updating, and can be reused in models throughout the rest of the application:
module Bang def bang(attributes) [*attributes].each do |attribute| key, value = attribute define_method("#{key}!") { update_attribute(value || key, send(key)) } end end end class User extend Bang bang :calculate_score => :score def calculate_score 100 end end
After the same pattern arose in another project, I wanted to extract the library to make it reusable. I decided against copying the library and having two versions to maintain, so I needed to turn it into an actual gem. However, it felt too small to squat a gem name on RubyGems.
Instead, I put the library into a Gist.
Gists are full Git repositories in disguise, so they can be cloned from and pushed to like other repositories.
To use it as a Ruby gem, add a gemspec
:
Gem::Specification.new do |s| s.name = 'bang' s.version = '0.1.0' s.platform = Gem::Platform::RUBY s.author = 'Jeff Kreeftmeijer' s.summary = 'Bang!' s.description = 'Bangs existing model methods' s.files = ['bang.rb'] s.test_file = 'bang_spec.rb' s.require_path = '.' s.add_development_dependency('rspec', ["~> 2.0"]) end
Then, install the library with Bundler by adding it to a Gemfile
in your project:
gem 'bang', :git => 'git://gist.github.com/1232884.git'