An oldish blog post I end up re-reading from time to time.

Evaluating Blocks Using The User’s Preference

I was looking through Slop’s source when I stumbled upon this little gem:

slop = Slop.new(options)
# ...
if block_given?
  block.arity == 1 ? yield(slop) : slop.instance_eval(&block)
end

Woah! Let’s test out how to use this:

class User
  
  class EditDSL
    # Simply accepts a block with the call method and returns 
    # the methods called as a Hash.
    def self.call(&blk)
      instance = new
      instance.instance_variable_set(:@result, {})
      blk.arity == 1 ? yield(instance) : instance.instance_eval(&blk)
      instance.instance_variable_get(:@result)
    end
    
    # Here is where you can define the methods for the call block.
    
    def name(new_name)
      @result[:name] = new_name
    end
    
    def email(new_email)
      @result[:email] = new_email
    end
  end
  
  attr_reader :name, :email
  
  def initialize(name, email)
    @name, @email = name, email
  end
  
  def edit(&blk)
    edited_attrs = EditDSL.call(&blk)
    edited_attrs.each do |attribute, value|
      instance_variable_set("@#{attribute}".to_sym, value)
    end
  end
end


my_user = User.new("John Doe", "john.doe@email.com")

p my_user.name # => "John Doe"
p my_user.email # => "john.doe@email.com"

my_user.edit do
  name "John Jacob Doe"
  email "john.jacob.doe@email.com"
end

p my_user.name # => "John Jacob Doe"
p my_user.email # => "john.jacob.doe@email.com"


my_user.edit do |u|
  u.name "John Jacob Doe III"
  u.email "john.jacob.doe.iii@email.com"
end

p my_user.name # => "John Jacob Doe III"
p my_user.email # => "john.jacob.doe.iii@email.com"

This is good for editing options/configurations for your library. Very cool.

Very Simple Edit-Once Gemspec
inline_testing

I’ve begun work on a new testing library called inline_testing, which allows yo to use your comments as tests! The gem is very young and barely works but I will be making a ton of progress on it in the coming weeks.

Example:

require 'inline_testing'
InlineTesting.init(__FILE__)

class User
  attr_accessor :name, :email
  def initialize(name, email)
    @name, @email = name, email
  end
end

InlineTesting.start(__LINE__)

 my_user = User.new("Ryan", "c00lryguy@gmail.com")
# some ignored comment
my_user.name # => "Ryan"
my_user.email # => "c00lryguy@gmail.com"

InlineTesting.stop(__LINE__)
InlineTesting.test(binding)

Don’t get it? Well these lines:

my_user.name # => "Ryan"
my_user.email # => "c00lryguy@gmail.com"

…are actually tests! we’re checking to see if my_user.name == "Ryan".

What it does is when you call InlineTesting.start(__LINE__), You’re telling my gem which line the tests are going to start, same with InlineTesting.stop(__LINE__). Then when you call InlineTesting.test(binding), my gem will process these lines and look for comments like # => which tells the gem what you expect the output of the line to be.

Like it said, it’s very immature but a ton of features are planned.

Gem

GitHub

Meta Tools

I’ve created the gem meta_tools when contains some methods for metaprogramming. These methods are heavily documented here.

If you think of anything to add, don’t be afraid to fork and create a pull request.

Gem: meta_tools
Git: meta_tools

Recursively Setting Deep Hash Value

I’ve always wanted to be able to do something like:

my_hash = {}
my_hash["Cloud"]["Stats"]["Strength"] = 100
p my_hash # => {"Cloud"=>{"Stats"=>{"Strength"=>100}}}

So I whipped up the code below and now I can! Metaprogramming wins again!

class SuperHash < Hash
  def initialize
    super { |h, k| h[k] = SuperHash.new }
  end
end

sh = SuperHash.new
sh["foo"]["bar"]["baz"] = "wtf"
pp sh

All this does is set the default value for the SuperHash to a new instance of SuperHash. Very…very cool.

Ruby CRUD Methods

Yesterday, I sat down and wrote out how I would like to write Ruby classes using the CRUD principle (I also wanted a gritty reboot of my RuleBook gem):

class User
  class_rule /^create_(male|female)$/ => :create
  instance_rule /^(name|age)$/ => :read
  instance_rule /^(name|age)_is$/ => :update
  instance_rule /^remove_(name|age)$/ => :destroy

  def self.create(gender)
    gender = gender.downcase.to_sym
    instance = new
    instance.instance_eval { @gender = gender }
    instance
  end
  
  def read(attribute)
    instance_variable_get("@#{attribute}")
  end

  def update(attribute, value)
    attribute = "@#{attribute}".downcase.to_sym
    instance_variable_set(attribute, value)
    self
  end

  def destroy(attribute)
    remove_instance_variable("@#{attribute}")
    self
  end
end

ry = User.create(:male)
puts "ry:", "  #{ry.inspect}"
nat = User.create_female
puts "nat:", " #{nat.inspect}"
george = User.create_male
puts "george:", "  #{george.inspect}"

ry.name_is("Ryan").age_is(19)
puts "ry:", "  #{ry.inspect}"

puts "ry:", "  age: #{ry.age}"

ry.remove_age
puts "ry:", "  #{ry.inspect}"

AHH SORCERY! Check out how it works: https://gist.github.com/750483

RuleBook 0.3.2 Released - 10x Faster!

Because of an awesome commit by Burke Libbey, RuleBook 0.3.2 has been released and is now 10 times faster!

Using this benchmark, here are the results:

0.3.1
    method defined with define_method:   0.0220010280609131
    method defined with rulebook:        1.00006604194641
    method defined by hand (with def):   0.0130009651184082
    method defined with eval (with def): 0.0130009651184082

0.3.2
    method defined with define_method:   0.0220010280609131
    method defined with rulebook:        0.161010980606079
    method defined by hand (with def):   0.0140011310577393
    method defined with eval (with def): 0.0119998455047607

Check out the code in the GitHub repository and feel free to fork!

RuleBook 0.3 Released!

I’ve just released RuleBook 0.3 which lets you define “rules” or methods defined with regex instead of strings.

What’s the big deal?

Well, this allows you to do some really cool things. Take this snippet for example:

  class User
    attr :title
    
    def is_a(title)
      @title = title
    end
    
    def is_a?(title)
      @title == title
    end
  end
  
  user = User.new
  user.is_a(:admin)
  user.is_a?(:moderator) # => false
  user.is_a?(:admin) # => true
  user.is_a(:something_we_dont_want) # works

This can be turned into:

  require 'rulebook'
  
  class User
    attr :title
    
    rule /is_(admin|moderator|super_user)/ do |status|
      @status = status.to_sym
    end
    
    rule /is_(admin|moderator|super_user)\?/ do |status|
      @status == status.to_sym
    end
  end
  
  user = User.new
  user.is_admin
  user.is_moderator? # => false
  user.is_admin? # => true
  user.is_something_we_dont_want # => No method error

We can also add dynamic class methods or ‘class_rules’.
Adding onto the previous code:

  class User
    class_rule /new_(admin|moderator|super_user)/ do |status|
      new.instance_eval { @status = status }
    end
  end
  
  user = User.new_admin
  user.is_admin? #=> true

Check out the code in the GitHub repository and feel free to fork!

Ruby SDoc In Chrome

I couple of months back, I released an RDoc Google Chrome extension which was a fairly simple wrapper for ruby-doc.com.

After using it for a little while, I got frustrated with having to scroll through all of the classes to find the documentation and decided it was time for something better, so I whipped up a wrapper for railsapi.com.

But, unfortunately for everyone involved, SDoc is made to be in frames, not separate tabs. So a little hacking is required:

  • Install Git and clone the base extension by running this:

    > git clone git@github.com:c00lryguy/rails_api_for_chrome.git

    This will create a new directory called rails_api_extension in your current directory


    If you do not want to install Git then you can navigate over to the project on Gitub and click ‘Download Source’ in the upper right-hand corner

    This will allow you to download a zip or tar of the project

  • Navigate to railsapi.com and select “Build your own package”
  • Select the packages you want included in your SDocs and click “Download”
  • Unzip the downloaded file and extract to your project directory
  • Modify line 401 of rails_api_extension/js/searchdoc.js to look like so:

    // Comment out line 401
    401     // this.frame.location.href = '../' + src;
    // Add this under line 401
    402     chrome.tabs.create({url : '../' + src});
  • Click ‘Developer mode’ to view the developer tools
  • Click ‘Load unpacked extension…’ and select your project directory
  • You can build your extension so it loads every time you load Chrome by clicking ‘Pack extension…’
  • To load a packed extension, just open Chrome and drag the rails_api_extension.crx file into Chrome