Entries Tagged 'bdd' ↓
September 12th, 2007 — ruby, rails, bdd
If you are running on edge rails with an app… lately, we’ve run into the error:
NameError: uninitialized constant ActionView::Helpers::JavaScriptMacrosHelper
YUCK!
Since that’s located in the spec plugin, I simple just commented it out…
Line 31 (rspec_on_rails/lib/spec/rails/dsl/behaviour/helper.rb)
Change
include ActionView::Helpers::JavaScriptMacrosHelper
to
# include ActionView::Helpers::JavaScriptMacrosHelper
Couldn’t be more simple.
Now… back to development.
Hope this helps someone else out there in google-land.
August 12th, 2007 — Blogroll, code, ruby, rails, bdd
Have you ever found yourself writing a code similar to this
[source:ruby]
class Post < ActiveRecord::Base
has_one :image
def get_image
image_id.nil? ? Image.default_image : Image.find(image_id)
end
end
[/source]
Where you have an instance where rather than catching an AR Exception, you want to have a default value for a model?
I noticed I was writing code like this, all the time
[source:ruby]
def get_image
begin
Image.find(params[:id])
rescue Exception => e
Image.default_image
end
end
[/source]
Imagine that, all over the place. How ugly!
So, rather than continue with this semi-ruby-style code, let’s use ActiveRecord and create ourselves a fun 20 minutes creating a fallback.
Let’s get started.
First things first, I like to place things in modules to separate the different methods, you may have your own thing, but… this is my article so I’ll tell you what works for me:
I usually start with a basic template when I am either a) creating a plugin that will extend an object or b) creating a file that will extend an object.
[source:ruby]
module CitrusByte
module ARFinderExtensions
def self.included(receiver)
receiver.extend MeatMethods
end
module MeatMethods
def something_here(*args)
extend CitrusByte::ARFinderExtensions::ClassMethods
include CitrusByte::ARFinderExtensions::InstanceMethods
end
end
module ClassMethods
end
module InstanceMethods
end
end
end
end
[/source]
In case you’ve never seen this schtuff before, it’s pretty cool. Check out the ruby docs for more information, but in a nutshell, if you place the something_here method inside a class, then that class with have the class methods and the instances will have the instance methods. It’s that simple. Check out the acts_as plugins. This is how they do it.
Anyways, so we have the basic template, now let’s fill some stuff in and get our find missing stuff moved into place.
Before we go any further, in my extensions, I am going to use a callback. This way we can have methods that operate at the class level and are defined on the instance and the two can fit seamlessly together.
So, what is a callback? Don’t tell me you haven’t used a callback before. The before_save, after_create methods are all callbacks that we use and take for granted as rails developers daily. Well, what are they already?
Callbacks are nothing more than attr_accessors that are accessible on the entire hierarchy of the instance. The difference is that the accessor in a callback is not the same as the one “owned” by the parent.
So now, we are going to use that to our advantage and define our own callback.
Time for some code (Going back to the example above… ):
[source:ruby]
def self.included(receiver)
receiver.extend MeatMethods
receiver.class_eval do
class << self
alias_method_chain :find, :catch
end
# Add the callback
def self.rescue_not_found(*callbacks, &block)
callbacks << block if block_given?
write_inheritable_array(:rescue_not_found, [block])
end
end
end
[/source]
Whenever any module is included into a class, the included method is called on the module. It’s ruby’s ultra-useful callback on itself. We can use it to do some really funky neat stuff. Can you think of other things we can use it for?
line 1 extends the MeatMethods (which we will deal with later) to the object
line 2 evaluates the block at the class level
lines 3-5 places inside the class, the ability to extend the instance with a class variable (confusing? Jay Fields has some awesome thoughts about this he hashes out Here) and alias_method_chain’s the methods find and catch.
If you don’t know what alias_method_chain does, ooo it’s slick. Basically it adds two methods to the class (first _with_ second AND first _without_ second) at hand and calls them in order. Wha? Meaning… for our circumstance, it will add find_with_catch and find_without_catch! How cool
line 7-10 have to do with the callbacks. First off, the callback name is going to be rescue_not_found. It is described as a block (as you are used to) and appends it , a block or not on to the inheritable_array.
How cool! I hope you are having fun, I sure am.
Now let’s actually add a method:
[source:ruby]
def find_with_catch(*args)
options = args.extract_options!
validate_find_options(options)
set_readonly_option!(options)
begin
case args.first
when :first then find_initial(options)
when :all then find_every(options)
else find_from_ids(args, options)
end
rescue Exception => e
callback :rescue_not_found
end
[/source]
This is basically the rails way of handling a find method with two major differences. First, this is the find_with_catch method that we defined above. Second, there is the rescue part which is what will be doing our work for us. Notice the rescue:
[source:ruby]
rescue Exception => e
callback :rescue_not_found
end
[/source]
If the ActiveRecord::Base#find can’t find an associated object in the database, it will throw a RecordNotFoundException… but in our scenario, we don’t want that… we want to have a method called instead, the entire point of our adventure! So we are going to catch that exception and instead, call the defined callback rescue_not_found.
There are a few more unimportant methods on the module, so I’ll skip those for now and you can check them out (including the callback method), but the one last important tidbit that you will need to know if you are going to use this fun-ness is that you actually have to add this stuff to ActiveRecord somehow… depending on the context, you could call the method defined above or just include it to the base…
For us, in this circumstance, we’ll use
[source:ruby]
ActiveRecord::Base.send(:include, CitrusByte::ARFinderExtensions)
[/source]
’cause we want all the finds to call the callback!
This allows you to do something like:
[source:ruby]
class Image < ActiveRecord::Base
rescue_not_found do
Image.new {:id => 1, :filename => “no_image.jpg”}.freeze
end
end
[/source]
This is much nicer than the former where a find had to be nested inside a begin;rescue;end statement.
Let me know how this works for you!
Also, you should stay tuned! Some big things are just on the horizon. I have a few plugins I am going to release here in a few days, I have a new blog posting coming out shortly too about rmagick and it’s use in production and I have a stellar joke in the mix too! That’s not all, but that’s I’ll I will say for now
active_record_extensions.rb
August 9th, 2007 — code, ruby, rails, merb, bdd
rSpec on merb.
Have you ever wanted to write a merb application and been turned off by the lack of merb/rSpec documentation? And if you are like me and have completely dumped Test::Unit and shudder at the idea that you might have to return to Test::Unit to reliably test an application written with the super-awesomely, fast(er) framework merb. Yeah. So totally me too!
After a few hours of headaches and digging through the rSpec and Test/Spec source and the sample MrBlog application on the merb site, I have a good platform to develop and test my merb application, reliably and as similar to the rails functionality as I can get it. So, without further ado and more discussion, here’s how I hacked it together:
First off, merb (0.3.7) loads all the plugins in both the dist (soon to be replaced by the directory app in 0.4.0) and the plugins directory at root (MERB_ROOT/plugins and DIST_ROOT/plugins) by requiring all the init.rb files in the directories. To get rSpec to work, you’ll probably need the rspec_on_merb plugin, by (as far as I can tell) Brian Takita. Feel free to put the plugin directory in either place, but for this app, I placed mine in the root. That becomes slightly important later.
I did this by creating the directory and typing
svn co http://svn.devjavu.com/merb/mrblog/trunk/plugins/rspec_on_merb/
.
After you have the plugin, we have to modify it a little. First, create an init.rb file in the plugin’s root (MERB_ROOT/plugins/rspec_on_merb/init.rb) and fill it out with the following:
dir = File.expand_path(File.dirname(__FILE__))
require "rubygems"
require "spec"
require "#{dir}/spec/spec_helper"
Dir["#{dir}/lib/*"].each do |file|
require file unless File.directory?(file)
end
Basically, this is going to load all the files in the plugin lib directory as well as the spec_helper, which you will definitely want. You can get rid of the files you won’t use, I didn’t use spec_suite at all, but you can use it if it suits you well.
Anyway, back to the process!
After that’s in place, you’ll want to modify the spec_helper in the directory (MERB_ROOT/plugins/spec/spec_helper.rb). The following is my entire spec_helper file. It should work for you right off the bat, but if not, let me know…
require "rubygems"
require "spec"
require "merb"
require 'active_record'
class SpecInitializer
attr_accessor :inst
module ClassMethods
def recreate_database_called?
@recreate_database_called
end
def recreate_database_called!
@recreate_database_called = true
end
def turn_off_printing
# TURN OFF PRINTING
$stdout = $stderr = File.open("/dev/null","a+");
end
def turn_on_printing
$stdout = $stderr = STDOUT
end
end
extend ClassMethods
def add_load_paths
$LOAD_PATH << "#{dir}/dist/app/controllers"
$LOAD_PATH << "#{dir}/dist/app/helpers"
$LOAD_PATH << "#{dir}/dist/app/models"
$LOAD_PATH << "#{dir}/dist/app/views"
end
def load_merb
require "#{dir}/../lib/spec/merb"
end
def run
@inst ||= self.class.new
@inst.add_load_paths
@inst.load_merb
@inst.initialize_database
@inst.require_project_files
@inst
end
def initialize_database
conn_options = YAML::load(Erubis::Eruby.new(IO.read("#{DIST_ROOT}/conf/database.yml")).result)
database = conn_options[:database]
username = conn_options[:username]
password = conn_options[:password]
unless recreate_database_called?
self.class.turn_off_printing
case conn_options[:adapter]
when 'mysql'
ActiveRecord::Base.connection.drop_database config[:database]
when /^sqlite/
FileUtils.rm_f File.join(RAILS_ROOT, config[:database])
when 'postgresql'
`dropdb "#{config[:database]}"`
end
self.class.turn_on_printing
end
# Connect to the database
ActiveRecord::Base.establish_connection conn_options["#{MERB_ENV}"]
unless recreate_database_called?
self.class.turn_off_printing
mig = []
Dir["#{DIST_ROOT}/schema/migrations/*"].each do |file|
require file; mig << File.basename(file, File.extname(file)).gsub(/^(d)+_/, '')
end
mig.each {|a| p a.classify.constantize}
mig.each {|a| a.classify.constantize.send :up}
self.class.turn_on_printing
end
# recreate_database_called! unless recreate_database_called?
end
def require_project_files
require_files_from_directory "controllers"
require_files_from_directory "helpers"
require_files_from_directory "models"
require_files_from_directory "views"
end
protected
def require_files_from_directory(app_dir)
Dir["#{dir}/dist/app/#{app_dir}/*"].each do |file|
require file
end
end
def recreate_database_called?
self.class.recreate_database_called?
end
def recreate_database_called!
self.class.recreate_database_called!
end
def dir
File.dirname(__FILE__)
end
end
I won’t go into detail as to what is going on, hopefully (and I suspect if you are using merb, you are somewhat confident with your rails understanding), but I will say that the output is suppressed ’cause we don’t want to see the database output everytime we spec, we just want to know if the spec passed or not. Feel free to add the printing in if you REALLY want to see it.
I added a custom directory in the plugin (MERB_DIR/rspec_on_merb/lib/custom) and that’s where I load the custom classes that I use like multipart.rb that you can find in the plugin’s helper currently. That’s for sure not necessary. I just like clean code bases. Anyway…
Now, I think we are done with the rspec_on_merb plugin directory. Let’s move to the test directory and get the specs going!
Make the following directory structure in your merb app (You can change this, of course, just pay attention to the require paths… if you change the directory, then you may have to change some of the require paths).
MERB_DIR/
…
test/
fixtures/
spec/
models/
controllers/
Okay, so now we are going to add all things you need to get the rSpec’s going green. In your spec directory (MERB_DIR/test/spec), add a file and call it spec_helper.rb (sound familiar? should).
The code I used for the spec_helper.rb is as follows
dir = File.expand_path(File.dirname(__FILE__))
$TESTING = true
$stdout = $stderr = File.open("/dev/null","a+")
require "rubygems"
require "spec"
require 'merb/merb_server'
dir = File.dirname(__FILE__)
Merb::Server.instance_eval {@@merb_raw_opts = []}
Merb::Server.merb_config
Merb::Server.config[:merb_root] = File.expand_path("#{dir}/../..")
Merb::Server.config[:dist_root] = File.expand_path("#{dir}/../../dist")
Merb::Server.config[:environment] = 'test'
require "merb"
require "active_record"
ActiveRecord::Base.verification_timeout = 14400
require "#{DIST_ROOT}/conf/merb_init"
require DIST_ROOT+"/conf/router.rb"
$LOAD_PATH << DIST_ROOT+"/app/models/"
$LOAD_PATH << DIST_ROOT+"/app/controllers/"
$LOAD_PATH << DIST_ROOT+"/app/helpers/"
Dir[DIST_ROOT+"/plugins/*/lib"].each { |m| $LOAD_PATH << m }
Dir[MERB_ROOT+"/plugins/*/lib"].each { |m| $LOAD_PATH << m }
require "spec/merb"
# Custom requires
require "#{dir}/login_service_helper"
$stdout = STDOUT
Spec::DSL::Behaviour.prepend_before(:each) { @si = SpecInitializer.new.run unless @no_refresh }
The important part of the spec_helper is the last line. That’s where you get the nice behavior that we are all used to from Rails. That’ll clear you database every test you run. Ugly, for sure.
In case you were wondering, the output (again) is surpressed in this block ’cause there are some print statements we don’t care about in there.
Okay, some more…
You definitely need a rake task. The one I use is as follows:
desc 'run specs'
task :specs => [:merb_init] do
Spec::Rake::SpecTask.new('specs') do |t|
t.spec_opts = ["--format", "specdoc"]
t.libs = ['lib', 'server/lib' ]
t.spec_files = FileList['test/spec/**/*_spec.rb']
end
end
I can’t remember (I’ve been hacking at this for a while) if the :merb_init task is apart of the Rakefile merb copies for you, so here is the one from my Rakefile
desc "load merb_init.rb"
task :merb_init do
require File.dirname(__FILE__)+'/dist/conf/merb_init.rb'
end
And I think you should be good to go…
This last tidbit bothered me for about an hour, so rather than have you dig around for it… if you are not familiar with the rSpec codebase, rSpec loads the specs and their inheritance tree based on the directory that your spec is located in. Unless you include the :behaviour_type => :controller on your controller specs, then you’ll get some pretty funky results. Like so:
describe Index, "controller", :behaviour_type => :controller do
end
It’s probably good practice to do that for all your describe blocks, but the model code worked for me.
Anyway, let me know if this works for you…
UPDATE
I had to manipulate some of the plugin to allow for RESTful testing:
in the controller behavior file: (MERB_DIR/plugins/rspec_on_merb/lib/spec/merb/dsl/behaviour/controller.rb) I changed the post method to look like (only the change is posted, everything else should stay the same):
method = opts.delete(:request_method) || 'POST'
body, head = m.prepare_query(opts)
request = Spec::Merb::Fakes::FakeRequest.new({:request_uri => path, :path_info => path.sub(/?.*$/,'')}, method)
request['REQUEST_METHOD'] = method
request['CONTENT_TYPE'] = head
request['CONTENT_LENGTH'] = body.length
request.post_body = body
I added the request_method to the parameters so you can “fake” the method in your test, like such:
def do_put
@response, @controller = post "/users/1.xml", @params.merge({:request_method => "DELETE"})
end
I’ll continue to update my changes when/if I make ‘em!