Posts Tagged Ruby

Round to half intervals (round with fractions) in Ruby

Let’s say you need to round to half numbers in Ruby, because you are making a rating system and want to display half stars for the ratings (1, 1.5, 2, 2.5, etc..). You want the following output when you are trying to round to the half fraction:

round_to_half( 1.6 )
=> 1.5

round_to_half( 1.7 )
=> 1.5

round_to_half( 1.8 )
=> 2.0

round_to_half( 2.1)
=> 2.0

round_to_half( 2.3 )
=> 2.5

etc…..

It is much simpler than I anticpated. You just need to multiply by the the inverse of the fraction (am I saying that right??), round the number and divide by the same number. So if you want to round to half places, you would do this:

1. flip 1/2 around to make it 2/1 (or 2)
2. multiply by the number you want to round
3. round the result from step 2
4. divide the result from step 3 by the number you got in step 1 (2 in this case)

Lets start with the first one in my example and run through the steps:

1. 2/1 => 2
2. 5.6 * 2 => 11.2
3. 11.2.round => 11
4. 11 / 2.0 => 5.5

And here it is in a Ruby method:

So you can pass any fraction to this method and it will round to that fraction. For example, you can try this with 20ths:

round_to_fraction(2.224, 1.0/20.0)
=> 2.2

round_to_fraction(2.225, 1.0/20.0)
=> 2.25

Remember to add the .0 to the end of the numbers you are dividing, so it forces ruby to treat it as a float. 1.0 / 20.0

Tags: , ,

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: , , , , , ,

Iterate over a date range in Ruby

I am trying to pull some reporting data from an API. I can only call it for one day at a time, since the API does not accept a date range. In order to use a date range I will have to make multiple calls to the API. Ruby can easily accomplish this be iterating thru a range of dates. I am going to pass a date range as 2 strings to my method and it will convert the strings to dates and iterate over the entire date range, and then call the API for each date. If no dates are passed to the method, then it assigns yesterday as the start and end date automatically. You can add some stuff for error checking for start date>end_date, but thats out of the scope for this tut. The Date class makes this really easy by giving us the upto method. It does all the work behind the scenes and you dont have to worry about crap like skipping the leap year.

Check it out:

def get_report_data(start_date=Time.now.yesterday,end_date=Time.now.yesterday)
  start_date.to_date.upto(end_date.to_date) do |day|
    get_data(day)
  end
end

Simplified with just a loop that prints dates:

"01/01/2010".to_date.upto("01-31-2010".to_date) do |day|
  puts day
end

Tags: , ,

star symbol in front of Ruby def argument

you may have seen something like this and wondered what the star symbol inside the argument parenthesis was:

def some_method(*args)
  #cowboy code goes here
end

The star symbol in front of args means that this method can accept an unlimited number of arguments. This is commonly used in Ruby on Rails when extracting options from the method arguments:

options = args.extract_options!

extract_options! is an ActiveSupport method that pulls out the option hashes from the method arguments.

Tags: ,

Ruby – cattr_accessor vs attr_accessor

What is the difference between cattr_accessor and attr_accessor? First off, cattr_accessor is part of Ruby on Rails ActiveSupport and not a feature of the Ruby language like attr_accessor.

cattr_accessor is class level equivalent of attr_accessor and can be classified as a singleton method. So cattr_accessor operates at the class level, while attr_accessor operates at the instance level.

Here is an example using a Counter class:

class Counter
  cattr_accessor :class_count
  attr_accessor :instance_count
end

counter1 = Counter.new
counter1.instance_count = 1
counter1.class_count = 1

counter2 = Counter.new
p counter2.instance_count
#> nil
p counter2.class_count
#> 1

As you can see the cattr_accessor stays the same for every instance, while the attr_accessor is only on the instance of the class.

Behind the scenes it is just using class and instance variables for the getter/setter methods.

@@class_count
@instance_count

Tags: ,

Ruby programming – what is dollar sign colon .unshift ($:.unshift) ??

This is a special variable in ruby. Ruby special variables start with the dollar sign followed by a single character. This particular variable is the default search path for load or require. If you call it in irb or the rails console, you can see it returns an array of strings which are paths.

Since it returns an array, the unshift is simply an array method that adds an object to the beginning of an array.

An example of this can be seen in the ActiveRecord source (2.3.2 rails). In this particular example, the ActiveSupport path is prepended to the array, so that it can be required in the code.

activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
  $:.unshift activesupport_path
  require 'active_support'
end

There are a bunch of other special variables in ruby like this one. Take a look here for more info: http://www.zenspider.com/Languages/Ruby/QuickRef.html#18

Tags: , ,

Using Ruby Threads

I have a fairly simple Ruby script that downloads 2 very large files from 2 different locations.  Long story short.. it is cheaper for me from a bandwidth perspective (b/c of 95th percentile billing) to download both of the files at the same time.  I can’t exactly do this in my Ruby script if I write it that executes sequentially.  So I thought back to my Java days (YUCK!)  and decided to see if Ruby had a similar mechanism as Java for threading.  Turns out it does and its pretty easy to use.  My requirements are to download both files and continue with the script only after both files are downloaded.  So here is what I did:

#create a thread to download first file
t1 = Thread.new do

system(“/usr/local/bin/ruby /path/to/my/file/start_download.rb”)

end

#start the 2nd download

start_download2

while t1.status!=false

sleep 1

end

#continue

…….

Easy enough. I start by firing off a thread to download the first file, then I start download the 2nd file within the main script.   Then I check the status of the thread to see if its complete.  If so, I continue the execute of the main script.

Tags: ,