An oldish blog post I end up re-reading from time to time.
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.
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.
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.
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
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.
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
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!
I’ve just released RuleBook 0.3 which lets you define “rules” or methods defined with regex instead of strings.
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!
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
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});